Json.NET get default values from auto-property initializer

懵懂的女人 提交于 2021-01-28 01:32:05

问题


I want to reduce the string Json.NET generates by using default values.

One of my properties is the following:

public string Name { get; set; } = "Jennifer";

I already use the auto-property initializer so the string gets populated if empty.

When serializing with Json.NET I use DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate so only changed properties get persisted.

I know that I can use the DefaultValueAttribut like this:

[DefaultValue("Jennifer")]
public string Name { get; set; } = "Jennifer";

But I am wondering if I can skip this attribute and use auto-property initial values as default value when serializing.


回答1:


Yes, you can do this using a custom ContractResolver such as the one below. It works by creating a reference instance for each Type (assuming the Type is a class and has a default constructor available) and then setting a ShouldSerialize predicate on each property which checks the current value of the property against the reference instance. If they match, then ShouldSerialize returns false and the property is not serialized.

class CustomResolver : DefaultContractResolver
{
    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        IList<JsonProperty> props = base.CreateProperties(type, memberSerialization);
        if (type.IsClass)
        {
            ConstructorInfo ctor = type.GetConstructor(Type.EmptyTypes);
            if (ctor != null)
            {
                object referenceInstance = ctor.Invoke(null);
                foreach (JsonProperty prop in props.Where(p => p.Readable))
                {
                    prop.ShouldSerialize = instance =>
                    {
                        object val = prop.ValueProvider.GetValue(instance);
                        object refVal = prop.ValueProvider.GetValue(referenceInstance);
                        return !ObjectEquals(val, refVal);
                    };
                }
            }
        }
        return props;
    }

    private bool ObjectEquals(object a, object b)
    {
        if (ReferenceEquals(a, b)) return true;
        if (a == null || b == null) return false;
        if (a is IEnumerable && b is IEnumerable && !(a is string) && !(b is string))
            return EnumerableEquals((IEnumerable)a, (IEnumerable)b);
        return a.Equals(b);
    }

    private bool EnumerableEquals(IEnumerable a, IEnumerable b)
    {
        IEnumerator enumeratorA = a.GetEnumerator();
        IEnumerator enumeratorB = b.GetEnumerator();
        bool hasNextA = enumeratorA.MoveNext();
        bool hasNextB = enumeratorB.MoveNext();
        while (hasNextA && hasNextB)
        {
            if (!ObjectEquals(enumeratorA.Current, enumeratorB.Current)) return false;
            hasNextA = enumeratorA.MoveNext();
            hasNextB = enumeratorB.MoveNext();
        }
        return !hasNextA && !hasNextB;
    }
}

To use the resolver you need to add it to the JsonSerializerSettings and pass the settings to the SerializeObject method like this:

JsonSerializerSettings settings = new JsonSerializerSettings
{
    ContractResolver = new CustomResolver(),
};
string json = JsonConvert.SerializeObject(yourObject, settings);

Here is a working demo: https://dotnetfiddle.net/K1WbSP

Some notes on this solution:

  • Using this resolver has a functional advantage over DefaultValue attributes in that it can handle complex defaults like Lists, Dictionaries and child objects (provided you have correctly implemented the Equals method on the child classes). Attributes can only handle simple constant expressions (e.g. strings, enums and other primitives). However, if all you need is simple defaults, be aware that this resolver will probably perform slightly worse than just using the attributes because it needs to use additional reflection to instantiate the reference objects and compare all the property values. So there is a little bit of a tradeoff. If your JSON is small, you probably won't notice a difference. But if your JSON is large, then you may want do some benchmarks.

  • You may want to implement a class-level opt-out mechanism (i.e. a custom attribute that the resolver looks for, or maybe a list of class names that you pass to the resolver) in case you run into situations where you do want the default values to be serialized for certain classes but not others.



来源:https://stackoverflow.com/questions/51691331/json-net-get-default-values-from-auto-property-initializer

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!