【问题标题】:How to default a null JSON property to an empty array during serialization with a List<T> property in JSON.NET?如何在 JSON.NET 中使用 List<T> 属性进行序列化期间将空 JSON 属性默认为空数组?
【发布时间】:2014-09-28 16:01:25
【问题描述】:

目前我有通过 HTTP 调用传入或存储在数据库中的 JSON,但在服务器处理期间它们被映射到 C# 对象。

这些对象具有public List&lt;int&gt; MyArray 之类的属性。

当 JSON 包含 MyArray:null 时,我希望结果属性为空的 List&lt;T&gt; 而不是空的 List&lt;T&gt; 属性。

目标是对象将“重新序列化”为 JSON 为 MyArray:[],从而保存到数据库或通过 HTTP 作为空数组而不是 null 响应。

这样,无论如何,C# 类基本上是为任何 List&lt;T&gt; 属性清理并强制执行一个空数组,否则该数组将是 null 并导致浏览器端代码中断(例如:@987654329 @)。

有没有办法在序列化/反序列化过程中让任何与List&lt;T&gt; 属性配对的空值变成一个空数组?

【问题讨论】:

    标签: c# arrays json.net


    【解决方案1】:

    如果空列表为空,您总是可以延迟加载空列表。

    使用 JsonDeserializer 上的 NullValueHandling 选项。

    var settings = new JsonSerializerSettings();
    settings.NullValueHandling = NullValueHandling.Ignore;
    
    return JsonConvert.DeserializeObject<T>(json, settings);
    

    http://james.newtonking.com/json/help/index.html?topic=html/SerializationSettings.htm

    【讨论】:

    【解决方案2】:

    我将建议使用自定义 JsonConverter 来解决此问题,但不会为空值调用转换器。相反,您需要将自定义IContractResolver 与自定义IValueProvider 结合使用。这是您需要的代码(灵感来自this answer):

    class NullToEmptyListResolver : DefaultContractResolver
    {
        protected override IValueProvider CreateMemberValueProvider(MemberInfo member)
        {
            IValueProvider provider = base.CreateMemberValueProvider(member);
    
            if (member.MemberType == MemberTypes.Property)
            {
                Type propType = ((PropertyInfo)member).PropertyType;
                if (propType.IsGenericType && 
                    propType.GetGenericTypeDefinition() == typeof(List<>))
                {
                    return new EmptyListValueProvider(provider, propType);
                }
            }
    
            return provider;
        }
    
        class EmptyListValueProvider : IValueProvider
        {
            private IValueProvider innerProvider;
            private object defaultValue;
    
            public EmptyListValueProvider(IValueProvider innerProvider, Type listType)
            {
                this.innerProvider = innerProvider;
                defaultValue = Activator.CreateInstance(listType);
            }
    
            public void SetValue(object target, object value)
            {
                innerProvider.SetValue(target, value ?? defaultValue);
            }
    
            public object GetValue(object target)
            {
                return innerProvider.GetValue(target) ?? defaultValue;
            }
        }
    }
    

    这是一个演示如何使用解析器:

    class Program
    {
        static void Main(string[] args)
        {
            JsonSerializerSettings settings = new JsonSerializerSettings();
            settings.ContractResolver = new NullToEmptyListResolver();
            settings.ObjectCreationHandling = ObjectCreationHandling.Replace;
            settings.Formatting = Formatting.Indented;
    
            Console.WriteLine("Serializing object with null lists...");
            Foo foo = new Foo();
            string json = JsonConvert.SerializeObject(foo, settings);
            Console.WriteLine(json);
            Console.WriteLine();
    
            Console.WriteLine("Deserializing JSON with null lists...");
            json = @"{ ""IntList"" : null, ""StringList"" : null }";
            foo = JsonConvert.DeserializeObject<Foo>(json, settings);
            Console.WriteLine("IntList size: " + foo.IntList.Count);
            Console.WriteLine("StringList size: " + foo.StringList.Count);
        }
    }
    
    class Foo
    {
        public List<int> IntList { get; set; }
        public List<string> StringList { get; set; }
    }
    

    输出:

    Serializing object with null lists...
    {
      "IntList": [],
      "StringList": []
    }
    
    Deserializing JSON with null lists...
    IntList size: 0
    StringList size: 0
    

    【讨论】:

    • 当列表属性存在/有任何数据时,这似乎不会反序列化;在这种情况下,它们会被反序列化为空值。您的示例不会往返(尝试反序列化原始序列化的结果)。有任何想法吗?我真的很喜欢这种方法,但它只是行不通——我需要在我的 json 源中使用一个为空的数组来反序列化为一个空列表,但是一个具有数据的数组可以使用所述数据反序列化到该列表。
    • @pinkfloydx33 你说得对——你需要将ObjectCreationHandling 设置为Replace 才能使往返正常工作。我已经修改了我的答案。添加此设置后,这是一个证明它有效的小提琴:dotnetfiddle.net/wsEyze
    • 酷,谢谢!在我逐字尝试您的代码然后尝试往返之前,我以为我会发疯。非常感谢
    【解决方案3】:

    在以下两种情况下,以下属性将在反序列化后分配空集合而不是 null:在 JSON 中省略该属性或明确设置为 null 时:

    class A
    {
        [JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
        public IEnumerable<int> Prop { get; set; } = new List<int>();
    }
    

    【讨论】:

      【解决方案4】:

      要在 .net 核心解决方案中使用 Brian Rogers 解决方案,您需要稍作修改才能访问“IsGenericType”属性,因为它从 Type 移动到 TypeInfo。

      标记为正确的答案(设置 NullValueHandling)对我不起作用,如果该属性为空,它只会忽略该属性。

      .net 核心的完整代码:

      using System;
      using System.Collections.Generic;
      using Newtonsoft.Json.Serialization;
      using System.Reflection;
      
      public class NullToEmptyListResolver : DefaultContractResolver
      {
          protected override IValueProvider CreateMemberValueProvider(MemberInfo member)
          {
              IValueProvider provider = base.CreateMemberValueProvider(member);
      
              if (member.MemberType == MemberTypes.Property)
              {
                  Type propType = ((PropertyInfo)member).PropertyType;
                  TypeInfo propTypeInfo = propType.GetTypeInfo();
                  if (propTypeInfo.IsGenericType &&
                      propType.GetGenericTypeDefinition() == typeof(List<>))
                  {
                      return new EmptyListValueProvider(provider, propType);
                  }
              }
      
              return provider;
          }
      
          class EmptyListValueProvider : IValueProvider
          {
              private IValueProvider innerProvider;
              private object defaultValue;
      
              public EmptyListValueProvider(IValueProvider innerProvider, Type listType)
              {
                  this.innerProvider = innerProvider;
                  defaultValue = Activator.CreateInstance(listType);
              }
      
              public void SetValue(object target, object value)
              {
                  innerProvider.SetValue(target, value ?? defaultValue);
              }
      
              public object GetValue(object target)
              {
                  return innerProvider.GetValue(target) ?? defaultValue;
              }
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-07-05
        • 1970-01-01
        • 2014-10-08
        • 2017-01-31
        • 1970-01-01
        • 2014-02-27
        • 1970-01-01
        • 2015-02-16
        相关资源
        最近更新 更多