如何忽略生成的 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类型并检查那里是否所有属性都是默认的。但是,例如,我想要自定义合同解析器形式的通用解决方案。

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

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

如果您设法使用 Regex barbaz删除最深级别的空对象(同时也意识到您需要删除它们之间的逗号以保持 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 将使用正则表达式。

相关内容

  • 没有找到相关文章

最新更新