Json.NET: How to make DefaultValueHandling only apply to certain types?

旧时模样 提交于 2020-05-31 07:27:07

问题


I have a custom class that looks like this:

public class PartnerLoginOptions
{
    public string Username { get; set; }
    public string Password { get; set; }
    public string DeviceModel { get; set; }
    public string Version { get; set; }

    public bool? IncludeUrls { get; set; }
    public bool? ReturnDeviceType { get; set; }
    public bool? ReturnUpdatePromptVersions { get; set; }
}

I'd like to ignore any bool? members with default values when serializing, but keep strings with null values. For example, if I had an object like this

var options = new PartnerLoginOptions
{
    Username = null,
    Password = "123",
    IncludeUrls = null,
    ReturnDeviceType = true
};

Then serializing would result in this:

{
    "username": null,
    "password": "123",
    "deviceModel": null,
    "version": null,
    "returnDeviceType": true
}

Here is the code I have so far:

var settings = new JsonSerializerSettings
{
    ContractResolver = new CamelCasePropertyNamesContractResolver(),
    DefaultValueHandling = DefaultValueHandling.Ignore // this applies to strings too, not just bool?
};

return JsonConvert.SerializeObject(options, settings);

Is there any way to do this without individually tagging each OptionalBool property with [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]? Thanks.


回答1:


You can create a custom contract resolver inheriting from DefaultContractResolver that applies DefaultValueHandling.Ignore to all properties of your required types. However, your specific question has some constraints:

  1. You want to apply a default value handling to a system type, namely bool?. Thus the contract resolver cannot be implemented by applying some custom attribute to the type, then checking for it in CreateProperty(). Instead it will be necessary to pass a collection of overrides to the contract resolver's constructor.

  2. You are using CamelCasePropertyNamesContractResolver. If you subclass it and pass different lists of overrides to different instances, you will encounter the bug from this question that contracts are forcibly shared across all instances of each subtype, even if different instances would otherwise return different contracts. Thus it will be necessary to inherit from DefaultContractResolver, which does not have this bug, and set DefaultContractResolver.NamingStrategy to a CamelCaseNamingStrategy.

Therefore your contract resolver should look like:

public class DefaultValueContractResolver : DefaultContractResolver
{
    readonly Dictionary<Type, DefaultValueHandling> overrides;

    public DefaultValueContractResolver(IEnumerable<KeyValuePair<Type, DefaultValueHandling>> overrides) : base()
    {
        if (overrides == null)
            throw new ArgumentNullException("overrides");
        this.overrides = overrides.ToDictionary(p => p.Key, p => p.Value);
    }

    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        var property = base.CreateProperty(member, memberSerialization);
        if (property.DefaultValueHandling == null)
        {
            DefaultValueHandling handling;
            if (overrides.TryGetValue(property.PropertyType, out handling))
                property.DefaultValueHandling = handling;
        }

        return property;
    }
}

Then use it like:

var options = new PartnerLoginOptions
{
    Username = null,
    Password = "123",
    IncludeUrls = null,
    ReturnDeviceType = true
};

var resolver = new DefaultValueContractResolver(
    new Dictionary<Type, DefaultValueHandling>
    { 
        { typeof(bool?), DefaultValueHandling.Ignore } 
    })
    {
        NamingStrategy = new CamelCaseNamingStrategy()
    };
var settings = new JsonSerializerSettings { ContractResolver = resolver };
var json = JsonConvert.SerializeObject(options, Formatting.Indented, settings);

Notes:

  • If you have a standard list of types for which you always apply a default value handling override, you should cache an instance of the contract resolver for best performance.

  • Contract resolver naming strategies were introduced in Json.NET 9.0.1. If you are using an earlier version and need camel casing, you will need to subclass DefaultValueContractResolver and add it yourself, e.g. as follows:

    public class DefaultValueCamelCaseContractResolver : DefaultValueContractResolver
    {
        public DefaultValueCamelCaseContractResolver(IEnumerable<KeyValuePair<Type, DefaultValueHandling>> overrides) : base(overrides) { }
    
        protected override string ResolvePropertyName(string propertyName)
        {
            return propertyName.ToCamelCase();
        }
    }
    
    public static class CamelCaseNameExtensions
    {
        class CamelCaseNameResolver : CamelCasePropertyNamesContractResolver
        {
            // Explicit static constructor to tell C# compiler not to mark type as beforefieldinit
            static CamelCaseNameResolver() { }
            internal static readonly CamelCaseNameResolver Instance = new CamelCaseNameResolver();
    
            // Purely to make the protected method public.
            public string ToCamelCase(string propertyName)
            {
                return ResolvePropertyName(propertyName);
            }
        }
    
        public static string ToCamelCase(this string propertyName)
        {
            if (propertyName == null)
                return null;
            return CamelCaseNameResolver.Instance.ToCamelCase(propertyName);
        }
    }
    

Demo fiddle here.



来源:https://stackoverflow.com/questions/37995738/json-net-how-to-make-defaultvaluehandling-only-apply-to-certain-types

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