我正在使用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
布尔方法,并检查所有属性是否都是默认属性。但是,我想以自定义合约解析器的形式提供一般解决方案。
答案 0 :(得分:5)
在评论中,您似乎决定使用Regex来摆脱空对象。这个想法的一个问题是它可能无法处理你将拥有我将要调用的情况"递归空对象"。换句话说就是这样:
{
"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()
创建它们的原始对象,因此无法获取{的值之后的{1}}属性。)
[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();
}
方法,以便它不会删除&#34;默认默认值&#34;的值。你可以把它减少到这个:
IsEmptyOrDefault()
答案 1 :(得分:0)
您可以使用JsonSerializerSettings
NullValueHandling.Ignore
var output = JsonConvert.SerializeObject(obj, new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore
});
如果此设置无法满足您的需求,请检查:the documentation.您可以找到所有属性和说明。
编辑:使用子(Sample)作为结构,它与DefaultValueHandling.Ignore一起使用。但由于类的复杂性,@ZoltánTamási将使用正则表达式。
答案 2 :(得分:0)
我实现了一个稍微不同的解决方案,它使用泛型方法,反射和一些默认的Newtonsoft.Json ShouldSerialize功能。不是优雅,但在概念上简单,我的特殊需要。以下是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;
}
}