如何在生成的JSON中省略/忽略/跳过空对象文字?

我正在使用Json.NET将复杂的C#对象图转换为JSON。 由于忽略了对象中具有默认值的属性,我通常在输出中得到空对象文字,我想省略它。

例如:

 public class Sample { public int Value { get; set; } public string Name { get; set; } } public class ParentSample { // this property should never be null, hence the initializer public Sample Sample { get; } = new Sample(); } .. var obj = new ParentSample(); // settings for indentation and excluding default values omitted for clarity var output = JsonConvert.SerializeObject(obj, ... ); // output will be // { // Sample: {} // } // // I'd like it to be // {} 

我知道一些特定类型的解决方案,比如将一个ShouldSerializeSample布尔方法添加到ParentSample类型,并检查所有属性是否都是默认属性。 但是,我想以自定义合约解析器的forms提供一般解决方案。

在评论中,您似乎决定使用正则表达式来摆脱空对象。 这个想法的一个问题是它可能无法处理你将拥有我称之为“递归空对象”的情况。 换句话说,这样的事情:

 { "foo": { "bar": {}, "baz": {} } } 

如果你设法删除最深层次的空对象barbaz与Regex(同时还意识到你需要删除它们之间的逗号以保持JSON有效),你仍然会留下一个空对象: foo

 { "foo": { } } 

我认为更好的解决方案是将数据加载到JToken层次结构中,然后使用递归方法删除所有空子项,然后再将其写入JSON。 这样的事情应该适合您的需求:

 using System; using Newtonsoft.Json.Linq; public static class JsonHelper { public static string SerializeToMinimalJson(object obj) { return JToken.FromObject(obj).RemoveEmptyChildren().ToString(); } public static JToken RemoveEmptyChildren(this JToken token) { if (token.Type == JTokenType.Object) { JObject copy = new JObject(); foreach (JProperty prop in token.Children()) { JToken child = prop.Value; if (child.HasValues) { child = child.RemoveEmptyChildren(); } if (!child.IsEmptyOrDefault()) { copy.Add(prop.Name, child); } } return copy; } else if (token.Type == JTokenType.Array) { JArray copy = new JArray(); foreach (JToken item in token.Children()) { JToken child = item; if (child.HasValues) { child = child.RemoveEmptyChildren(); } if (!child.IsEmptyOrDefault()) { copy.Add(child); } } return copy; } return token; } public static bool IsEmptyOrDefault(this JToken token) { return (token.Type == JTokenType.Array && !token.HasValues) || (token.Type == JTokenType.Object && !token.HasValues) || (token.Type == JTokenType.String && token.ToString() == String.Empty) || (token.Type == JTokenType.Boolean && token.Value() == false) || (token.Type == JTokenType.Integer && token.Value() == 0) || (token.Type == JTokenType.Float && token.Value() == 0.0) || (token.Type == JTokenType.Null); } } 

然后,您可以像这样序列化您的对象:

 var json = JsonHelper.SerializeToMinimalJson(obj); 

小提琴: https : //dotnetfiddle.net/awRPMR

编辑

如果要使用此方法遵循[DefaultValue]属性,可以通过修改SerializeToMinimalJson()方法来创建JsonSerializer的实例,在JsonSerializer设置DefaultValueHandling属性,然后将其传递给JToken.FromObject()如下所示。 (必须以这种方式完成,因为JTokens没有引用回原始对象,使用FromObject()创建它们,因此在此之后无法获取[DefaultValue]属性的值。)

 public static string SerializeToMinimalJson(object obj) { var serializer = new JsonSerializer(); serializer.NullValueHandling = NullValueHandling.Ignore; serializer.DefaultValueHandling = DefaultValueHandling.Ignore; return JToken.FromObject(obj, serializer).RemoveEmptyChildren().ToString(); } 

如果这样做,您可能还需要更改IsEmptyOrDefault()方法,以便它不会删除作为“默认默认值”的值。 你可以把它减少到这个:

 public static bool IsEmptyOrDefault(this JToken token) { return (token.Type == JTokenType.Array && !token.HasValues) || (token.Type == JTokenType.Object && !token.HasValues); } 

小提琴: https : //dotnetfiddle.net/0yVRI5

您可以使用JsonSerializerSettings为方法提供NullValueHandling.Ignore

 var output = JsonConvert.SerializeObject(obj, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore }); 

如果此设置无法满足您的需求,请查看: 文档。 在那里你可以找到所有的属性和描述。

编辑:使用子(Sample)作为结构,它与DefaultValueHandling.Ignore一起使用。 但由于类的复杂性,@ZoltánTamási将使用正则表达式。

我实现了一个稍微不同的解决方案,它使用generics方法,reflection和一些默认的Newtonsoft.Json ShouldSerializefunction。 不优雅,但在概念上简单,我的特殊需要。 以下是LinqPad代码片段。

 void Main() { Person person = new Person(); person.MyAddress = new Address(); var ret = person.ShouldSerializeMyAddress(); var json = JsonConvert.SerializeObject(person, Newtonsoft.Json.Formatting.Indented, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); json.Dump(); } public static class JsonExtensions { public static bool ShouldSerialize(this object self) { if (self == null) return false; var methods = self.GetType().GetMethods().Where(p => p.Name.StartsWith("ShouldSerialize")); return methods.Any(p => p.Invoke(self, null) is bool value && value); } } public class Person { public Address MyAddress { get; set; } public bool ShouldSerializeMyAddress() { return MyAddress.ShouldSerialize(); } } public class Address { public string Street { get; set; } public bool ShouldSerializeStreet() { return false; // or whatever your property serialization criteria should be } public string City { get; set; } public bool ShouldSerializeCity() { return false; } public string State { get; set; } public bool ShouldSerializeState() { return false; } public string Zip { get; set; } public bool ShouldSerializeZip() { return false; } }