Newtonsoft.Json CustomContractResolver排除空对象节点

时间:2017-04-12 04:11:11

标签: c# json parsing json.net

我正在尝试序列化一组包含各种类型的对象,包括对其他自定义类型的对象引用。我希望在它的属性成员都是默认值或null时排除这些对象引用。这是设置:

public class ObjectA
{
    [DefaultValue(2)]
    [JsonProperty(PropertyName = "propertyA")]
    public int PropertyA { get; set; } = 6;

    [JsonProperty(PropertyName = "objectB")]
    public ObjectB ObjectB { get; set; } = new ObjectB();
}

public class ObjectB
{
    [DefaultValue(2)]
    [JsonProperty(PropertyName = "propertyA")]
    public int PropertyA { get; set; } = 2;

    [JsonProperty(PropertyName = "propertyB")]
    public string PropertyB { get; set; }
}

问题在于,当我使用以下内容序列化ObjectA时:

var settings = new JsonSerializerSettings();

settings.NullValueHandling = NullValueHandling.Ignore;
settings.DefaultValueHandling = DefaultValueHandling.Ignore;

return JsonConvert.SerializeObject(ObjectA, settings);

我想看到这个:

{
    "propertyA": 6
}

但是我仍然看到一个空的对象属性引用:

{
    "propertyA": 6,
    "objectB" : {}
}

我想摆脱json中的objectB,只有当其中一个成员不是默认值或null时才显示它。虽然此示例仅显示一个嵌套级别,但对于任何级别的对象嵌套都需要保持为真。

2 个答案:

答案 0 :(得分:0)

问题在于ObjectB本身的默认值是一个对象,而不是序列化ObjectA时它具有的属性的默认值。

如果您浏览示例here,他们就提到了nullable类型的预期默认值以及object的{​​{1}}

的默认值
  

此选项将忽略所有默认值(例如,对象和可空类型为null;对于整数,小数和浮点数为0;对于布尔值为false)。

为说明含义,请尝试使用默认值<{1}}序列化

null

你得到的是ObjectB

现在,如果您明确var objectB = new ObjectB { PropertyA = 2 //The default value is also 2. }; string serialized = JsonConvert.SerializeObject(objectB, Newtonsoft.Json.Formatting.Indented, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Ignore }); 设置为{},那么序列化程序只会将其忽略为ObjectB -

null

您将得到预期的结果 -

object

您可以尝试使用不同的值,看看这是否符合您的期望。

答案 1 :(得分:0)

所以我已经找到了一个丑陋的解决方案,它可以递归地减少空的Json节点,同时维护默认情况下实例化的嵌套对象。该解决方案涉及使用DefaultContractResolver实现,该实现使用递归和某些类型映射来减少Json。

这是合同解析员:

public class ShouldSerializeContractResolver : DefaultContractResolver
{
    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        JsonProperty property = base.CreateProperty(member, memberSerialization);

        if (property.GetType().GetTypeName() == "object")
        {
            property.ShouldSerialize =
                instance =>
                {
                    var value = instance.GetType().GetProperty(property.UnderlyingName).GetValue(instance, null);

                    if (value == null)
                    {
                        return false;
                    }

                    if (value.GetType().GetTypeName() == "object")
                    {
                        if (NodeHasValue(value))
                        {
                            return true;
                        }
                    }
                    else
                    {
                        if (value.GetType().GetTypeName() == "collection")
                        {
                            ICollection enumerable = (ICollection)value;
                            if (enumerable.Count != 0)
                            {
                                return true;
                            }
                            else
                            {
                                return false;
                            }
                        }

                        return true;
                    }
                    return false;

                };
        }

        return property;
    }

    public bool NodeHasValue(object obj)
    {
        Type objType = obj.GetType();
        PropertyInfo[] properties = objType.GetProperties();

        foreach (PropertyInfo property in properties)
        {
            var value = property.GetValue(obj, null);

            if (value == null)
            {
                return false;
            }

            if (value.GetType().GetTypeName() == "object")
            {
                return NodeHasValue(value);
            }

            if (value.GetType().GetTypeName() == "collection")
            {
                ICollection enumerable = (ICollection)value;
                if (enumerable.Count != 0)
                {
                    return true;
                }
            }

            if (value.GetType().GetTypeName() == "array")
            {
                IList enumerable = (IList)value;
                if (enumerable.Count != 0)
                {
                    return true;
                }
            }

            if (value.GetType().GetTypeName() != "object" 
                && value.GetType().GetTypeName() != "collection" 
                && value.GetType().GetTypeName() != "array")
            {
                if (value != null)
                {
                    var attribute = property.GetCustomAttribute(typeof(DefaultValueAttribute)) as DefaultValueAttribute;

                    if (attribute.Value.ToString() != value.ToString())
                    {
                        return true;
                    }
                }
            }
        }

        return false;
    }
}

方法GetTypeName()是Type类的扩展方法,它用于标识我指定的基本类型与集合,对象和数组。

GetTypeName()的扩展方法类:

public static string GetTypeName(this Type type)
{
    if (type.IsArray)
    {
        return "array";
    }

    if (type.GetTypeInfo().IsGenericType)
    {
        if (type.GetGenericTypeDefinition() == typeof(Nullable<>))
        {
            return GetTypeName(Nullable.GetUnderlyingType(type)) + '?';
        }

        var genericTypeDefName = type.Name.Substring(0, type.Name.IndexOf('`'));
        var genericTypeArguments = string.Join(", ", type.GenericTypeArguments.Select(GetTypeName));

        if (type.GetTypeInfo().GetInterfaces().Any(ti => ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)))
        {
            return "collection";
        }

        return $"{genericTypeDefName}<{genericTypeArguments}>";
    }

    string typeName;
    if (_primitiveTypeNames.TryGetValue(type, out typeName))
    {
        return typeName;
    }

    // enum's come up as a ValueType so we check IsEnum first.
    if (type.GetTypeInfo().IsEnum)
    {
        return "enum";
    }

    if (type.GetTypeInfo().IsValueType)
    {
        return "struct";
    }

    return "object";
}

private static readonly Dictionary<Type, string> _primitiveTypeNames = new Dictionary<Type, string>
{
    { typeof(bool), "bool" },
    { typeof(byte), "byte" },
    { typeof(byte[]), "byte[]" },
    { typeof(sbyte), "sbyte" },
    { typeof(short), "short" },
    { typeof(ushort), "ushort" },
    { typeof(int), "int" },
    { typeof(uint), "uint" },
    { typeof(long), "long" },
    { typeof(ulong), "ulong" },
    { typeof(char), "char" },
    { typeof(float), "float" },
    { typeof(double), "double" },
    { typeof(string), "string" },
    { typeof(decimal), "decimal" }
};

}