json serializer NullValueHandling without use datamember attribute

醉酒当歌 提交于 2019-12-20 04:55:12

问题


In my Web api project, Right now I'm skipping null values. therefore, the return json ignores null values and prints the property.

In Global.asax file:

//manage the null in the response
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;

However, I want to replace the null values by "-". but, i dont want to use data member attribute for each property...

[DefaultValue("-")]. 

i have more than 10 classes in my project... so, It is not the most elegant solution.

I wish were a simple solution and apply to any conversion, as does with null values from the Global.asax

Example.

public class User
{
    public string user { get; set; }

    public string name { get; set; }

    public string dni { get; set; }
}

when exists all data, my service return

{
  "user": "usertest",
  "name": "nametest",
  "dni": "123456789"
}

But, when dni, doesn't exists, respond this

{
  "user": "usertest",
  "name": "nametest",
  "dni": ""
}

So, I would like to respond as follows

{
  "user": "usertest",
  "name": "nametest",
  "dni": "-"
}

回答1:


You can handle this with a custom IContractResolver. The resolver can apply an IValueProvider to each and every string property, which would then handle the conversion of null values to - (and back, if you are deserializing the same JSON).

Here is the code you would need for the resolver:

public class NullStringReplacementResolver : DefaultContractResolver
{
    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        IList<JsonProperty> props = base.CreateProperties(type, memberSerialization);

        // Attach a NullStringReplacementProvider instance to each string property
        foreach (JsonProperty prop in props.Where(p => p.PropertyType == typeof(string)))
        {
            PropertyInfo pi = type.GetProperty(prop.UnderlyingName);
            if (pi != null)
            {
                prop.ValueProvider = new NullStringReplacementProvider(pi);
            }
        }

        return props;
    }

    protected class NullStringReplacementProvider : IValueProvider
    {
        PropertyInfo targetProperty;

        public NullStringReplacementProvider(PropertyInfo targetProperty)
        {
            this.targetProperty = targetProperty;
        }

        // GetValue is called by Json.Net during serialization.
        // The target parameter has the object from which to read the string;
        // the return value is the string that gets written to the JSON
        public object GetValue(object target)
        {
            // if the value of the target property is null, replace it with "-"
            string s = (string)targetProperty.GetValue(target);
            return (s == null ? "-" : s);
        }

        // SetValue gets called by Json.Net during deserialization.
        // The value parameter has the original value read from the JSON;
        // target is the object on which to set the value.
        public void SetValue(object target, object value)
        {
            // if the value in the JSON is "-" replace it with null
            string s = (string)value;
            targetProperty.SetValue(target, s == "-" ? null : s);
        }
    }
}

To use the custom resolver, you need to add it to the JsonSerializerSettings that are used during serialization and deserialization. If you're using ASP.NET Web API, you can do that by adding the following to the Application_Start method in Global.asax.cs:

var config = GlobalConfiguration.Configuration;
var settings = config.Formatters.JsonFormatter.SerializerSettings;
settings.ContractResolver = new NullStringReplacementResolver();

Fiddle: https://dotnetfiddle.net/FVA3p8




回答2:


I don't recommend doing this. The problem is that if one if your string-value properties happens to have the same value as your "null value" "-", you will lose information when serializing, namely whether or not the property was actually null.

That being said, you can do this with a ContractResolver by overriding the JsonProperty.ValueProvider for string-valued properties:

public class StringRemappingContractResolver : DefaultContractResolver
{
    static readonly object NullValue;

    static StringRemappingContractResolver() { NullValue = new object(); }

    readonly Dictionary<object, object> map;
    readonly ILookup<object, object> reverseMap;

    public StringRemappingContractResolver() : this(new KeyValuePair<object, object> [] { new KeyValuePair<object, object>(null, "-")}) 
    {
    }

    public StringRemappingContractResolver(IEnumerable<KeyValuePair<object, object>> map)
    {
        if (map == null)
            throw new ArgumentNullException("map");
        this.map = map.ToDictionary(p => p.Key ?? NullValue, p => p.Value);
        this.reverseMap = map.ToLookup(p => p.Value ?? NullValue, p => p.Key);
    }

    class StringRemappingValueProvider : IValueProvider
    {
        readonly IValueProvider baseProvider;
        readonly Dictionary<object, object> map;
        readonly ILookup<object, object> reverseMap;

        public StringRemappingValueProvider(IValueProvider baseProvider, Dictionary<object, object> map, ILookup<object, object> reverseMap)
        {
            if (baseProvider == null)
                throw new ArgumentNullException("baseProvider");
            this.baseProvider = baseProvider;
            this.map = map;
            this.reverseMap = reverseMap;
        }

        #region IValueProvider Members

        public object GetValue(object target)
        {
            var value = baseProvider.GetValue(target);
            object mapped;
            if (map.TryGetValue(value ?? NullValue, out mapped))
                value = mapped;
            return value;
        }

        public void SetValue(object target, object value)
        {
            foreach (var mapped in reverseMap[value ?? NullValue])
            {
                // Use the first.
                value = mapped;
                break;
            }
            baseProvider.SetValue(target, value);
        }

        #endregion
    }

    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        var property = base.CreateProperty(member, memberSerialization);
        if (property.PropertyType == typeof(string))
        {
            property.ValueProvider = new StringRemappingValueProvider(property.ValueProvider, map, reverseMap);
        }
        return property;
    }
}

Then use it like:

public class User
{
    public string user { get; set; }

    public string name { get; set; }

    public string dni { get; set; }
}

public class TestClass
{
    public static void Test()
    {
        var settings = new JsonSerializerSettings { ContractResolver = new StringRemappingContractResolver() };

        var user = new User { user = "usertest", name = "nametest", dni = null };
        var json = JsonConvert.SerializeObject(user, settings);
        Debug.WriteLine(json); // Prints {"user":"usertest","name":"nametest","dni":"-"}
        Debug.Assert(JToken.DeepEquals(JToken.Parse(json), JToken.Parse(@"{'user':'usertest','name':'nametest','dni':'-'}"))); // No assert
        var userBack = JsonConvert.DeserializeObject<User>(json, settings);
        Debug.Assert(user.dni == userBack.dni && user.name == userBack.name && user.user == userBack.user); // No assert
    }
}



回答3:


You can register a converter in the globals.asax. You would add something like this:

config.Formatters.JsonFormatter.SerializerSettings.Converters.add(new NullHandlerConverter());

Where, NullHandlerConverter is a custom converter that you will need to write.

Edit: Brian is right. Converters cannot be used since there is no way to have them called on null values.



来源:https://stackoverflow.com/questions/32787256/json-serializer-nullvaluehandling-without-use-datamember-attribute

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