我正在使用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
类型并检查那里是否所有属性都是默认的。但是,例如,我想要自定义合同解析器形式的通用解决方案。
在评论中,您似乎决定使用正则表达式来摆脱空对象。 这个想法的一个问题是,它可能无法处理我称之为"递归空对象"的情况。 换句话说,像这样:
{
"foo":
{
"bar": {},
"baz": {}
}
}
如果您设法使用 Regex bar
和baz
删除最深级别的空对象(同时也意识到您需要删除它们之间的逗号以保持 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<JProperty>())
{
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<bool>() == false) ||
(token.Type == JTokenType.Integer && token.Value<int>() == 0) ||
(token.Type == JTokenType.Float && token.Value<double>() == 0.0) ||
(token.Type == JTokenType.Null);
}
}
然后,您可以像这样序列化对象:
var json = JsonHelper.SerializeToMinimalJson(obj);
小提琴:https://dotnetfiddle.net/awRPMR
编辑
如果要使用此方法遵循 [DefaultValue]
属性,可以通过修改 SerializeToMinimalJson()
方法创建 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
我实现了一个稍微不同的解决方案,它使用通用方法,反射和一些默认的Newtonsoft.Json应该序列化功能。不优雅,但概念上很简单,满足我的特定需求。下面是 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;
}
}
NullValueHandling.Ignore
对该方法进行JsonSerializerSettings
:
var output = JsonConvert.SerializeObject(obj, new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore
});
如果此设置未提供您需要的内容,请查看:文档。在那里,您可以找到所有属性和说明。
编辑:使用子(样本)作为结构,它与DefaultValueHandling.Ignore一起工作。但是@Zoltán由于类的复杂性,Tamási 将使用正则表达式。