Newtonsoft.Json CustomContractResolver 排除空对象节点

Jam*_*mie 6 c# parsing json 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; }
}
Run Code Online (Sandbox Code Playgroud)

问题是当我使用以下命令序列化 ObjectA 时:

var settings = new JsonSerializerSettings();

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

return JsonConvert.SerializeObject(ObjectA, settings);
Run Code Online (Sandbox Code Playgroud)

我想看看这个:

{
    "propertyA": 6
}
Run Code Online (Sandbox Code Playgroud)

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

{
    "propertyA": 6,
    "objectB" : {}
}
Run Code Online (Sandbox Code Playgroud)

我想删除 json 中的 objectB,并且仅当它的成员之一不是默认值或 null 时才显示它。虽然此示例仅显示一层嵌套,但它需要适用于任何级别的对象嵌套。

Jam*_*mie 1

所以我制定了一个丑陋的解决方案,它可以递归地减少空的 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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" }
};
Run Code Online (Sandbox Code Playgroud)

}