【问题标题】:json serializer NullValueHandling without use datamember attributejson序列化器NullValueHandling不使用数据成员属性
【发布时间】:2015-09-25 17:04:29
【问题描述】:

在我的 Web api 项目中,现在我正在跳过空值。因此,返回 json 会忽略 null 值并打印属性。

在 Global.asax 文件中:

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

但是,我想用“-”替换空值。但是,我不想为每个属性使用数据成员属性...

[DefaultValue("-")]. 

我的项目中有超过 10 个类...所以,这不是最优雅的解决方案。

我希望是一个简单的解决方案并适用于任何转换,就像 Global.asax 中的空值一样

示例。

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

    public string name { get; set; }

    public string dni { get; set; }
}

当所有数据都存在时,我的服务返回

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

但是,当 dni,不存在时,响应这个

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

所以,我想回复如下

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

【问题讨论】:

  • 您介意澄清一下您的问题吗?您的意思是要跳过写入当前值与其默认值匹配的成员,还是在写入具有空值的成员时,将写入的值替换为默认值?
  • 另外,您的意思是您不想为每个属性使用[DefaultValue]DefaultValueAttribute 不是数据成员属性,它用于许多事情,包括属性编辑器。
  • 好的,已编辑。我不想使用默认属性。 [默认值(”-”)]。我只需要一种简单而优雅的方式来为所有属性设置 json 序列化器的 DefaultValue。
  • 您介意为您的问题创建一个mcve 吗?即使在问题编辑之后,我也无法理解您想要做什么。您想 1) Convert 空值到“-”值吗? 2) 跳过值为"-"的成员? 3) 将仅字符串值属性的空值转换为"-"?
  • @dbc 我想 1) 是的!

标签: c# json json.net asp.net-web-api


【解决方案1】:

您可以使用自定义 IContractResolver 处理此问题。解析器可以将IValueProvider 应用于每个字符串属性,然后将空值转换为-(如果您要反序列化相同的JSON,则返回)。

这是解析器所需的代码:

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);
        }
    }
}

要使用自定义解析器,您需要将其添加到在序列化和反序列化期间使用的JsonSerializerSettings。如果您使用的是 ASP.NET Web API,您可以通过将以下内容添加到 Global.asax.cs 中的 Application_Start 方法来实现:

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

小提琴:https://dotnetfiddle.net/FVA3p8

【讨论】:

    【解决方案2】:

    我不建议这样做。问题是,如果您的字符串值属性恰好与您的“空值”"-" 具有相同的值,那么您将在序列化时丢失信息,即该属性是否实际上为空。

    话虽如此,您可以通过覆盖字符串值属性的JsonProperty.ValueProvider 来使用ContractResolver 执行此操作:

    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;
        }
    }
    

    然后像这样使用它:

    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】:

      您可以在 globals.asax 中注册转换器。你会添加这样的东西:

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

      其中,NullHandlerConverter 是您需要编写的自定义转换器。

      编辑:Brian 是对的。无法使用转换器,因为无法在 null 值上调用它们。

      【讨论】:

      • 不幸的是,JsonConverter 对此不起作用,因为在序列化过程中不会为空值调用转换器。小提琴:dotnetfiddle.net/LmcroE
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-11-13
      • 2017-04-02
      • 2011-08-27
      • 2018-06-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多