json.net - 如何使用 JsonConvert.DeserializeObject 忽略空数组?

标签 json.net

我正在使用此调用从 JSON 中读取对象列表:

Rootobject userInfo = JsonConvert.DeserializeObject<Rootobject>(File.ReadAllText(strFileName));

但我得到一个异常(exception) Cannot deserialize the current JSON array .如果类对象之一中的数组之一为空。只要有数据,一切正常。

下面是一个 JSON 的例子,它导致了反序列化器:

这是 Venue 对象的正常数据类型:
"venue":  {
            "venue_id":  696895,
            "venue_name":  "Blackfinn Ameripub",
            "venue_slug":  "blackfinn-ameripub",
            "primary_category":  "Food",
            "parent_category_id":  "4d4b7105d754a06374d81259",
            "categories":  {
                "count":  1,
                "items":  [
                            {
                                "category_name":  "American Restaurant",
                                "category_id":  "4bf58dd8d48988d14e941735",
                                "is_primary":  true
                            }
                        ]
            },
            "is_verified":  false
        },

这是导致异常的原因,一个空数组:
"venue":  [

        ],

我曾尝试使用 JsonSerializerSettings选项包括 DefaultValueHandling , NullValueHandlingMissingMemberHandling但它们似乎都不能阻止错误。

知道如何反序列化 JSON 并忽略数据中的任何空数组吗?我希望它可以处理任何空数组,而不仅仅是上面针对对象 Venue 的示例.

New issue was found - 03/17/2018 <<



嗨,下面的转换器一直工作得很好,但是我从我的 json 响应中得到的服务器又提出了另一个挑战。 JSON.NET 检索此类数据没有问题:
 "toasts":  {
                "total_count":  1,
                "count":  1,
                "auth_toast":  false,
                "items":  [
                              {
                                  "uid":  3250810,
                                  "user":  {
                                               "uid":  3250810,
                                               "account_type":  "user",
                                               "venue_details":  [

                                                                 ],
                                               "brewery_details":  [

                                                                   ]
                                           },
                                  "like_id":  485242625,
                                  "like_owner":  false,
                                  "created_at":  "Wed, 07 Mar 2018 07:54:38 +0000"
                              }
                          ]
            },

特别是具有venue_details 的部分。 99% 的回复都以这种格式返回了venue_details:
 "venue_details":  [

                   ],

但后来我突然得到了这种格式:
 "toasts":  {
                "total_count":  1,
                "count":  1,
                "auth_toast":  false,
                "items":  [
                              {
                                  "uid":  4765742,
                                  "user":  {
                                               "uid":  4765742,
                                               "account_type":  "venue",
                                               "venue_details":  {
                                                                     "venue_id":  4759473
                                                                 },
                                               "brewery_details":  [

                                                                   ],
                                               "user_link":  "https://untappd.com/venue/4759473"
                                           },
                                  "like_id":  488655942,
                                  "like_owner":  false,
                                  "created_at":  "Fri, 16 Mar 2018 16:47:10 +0000"
                              }
                          ]
            },

注意venue_details 现在有一个值并包含一个venue_id。
因此,venue_details 最终看起来像一个对象而不是一个数组。这最终给出了这个异常(exception):

JsonSerializationException:无法将当前 JSON 对象(例如 {"name":"value"})反序列化为类型“System.Collections.Generic.List`1[System.Object]”,因为该类型需要一个 JSON 数组(例如 [1, 2,3]) 以正确反序列化。

在提供的转换器代码中,该异常发生在旁边带有 *s 的这一行中:
public abstract class IgnoreUnexpectedArraysConverterBase : JsonConverter
{
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var contract = serializer.ContractResolver.ResolveContract(objectType);
        if (!(contract is JsonObjectContract))
        {
            throw new JsonSerializationException(string.Format("{0} is not a JSON object", objectType));
        }

        do
        {
            if (reader.TokenType == JsonToken.Null)
                return null;
            else if (reader.TokenType == JsonToken.Comment)
                continue;
            else if (reader.TokenType == JsonToken.StartArray)
            {
                var array = JArray.Load(reader);
                if (array.Count > 0)
                    throw new JsonSerializationException(string.Format("Array was not empty."));
                return existingValue ?? contract.DefaultCreator();
            }
            else if (reader.TokenType == JsonToken.StartObject)
            {
                // Prevent infinite recursion by using Populate()
                existingValue = existingValue ?? contract.DefaultCreator();
            *** serializer.Populate(reader, existingValue); ***
                return existingValue;

任何想法如何添加这个额外的处理来解释在 JSON 返回对象而不是数组之间的这种翻转?

谢谢,
瑞克

最佳答案

你的问题不是你需要忽略空数组。如果"items"数组为空,就不会有问题:

"items":  [],

相反,您的问题如下。 JSON standard支持两种类型的容器:
  • 数组,它是值的有序集合。数组以 [ 开头(左括号)并以 ] 结尾(右括号)。值由 , 分隔(逗号)。
  • 对象,它是一组无序的名称/值对。对象以 { 开头(左括号)并以 } 结尾(右括号)。

  • 由于某种原因,服务器返回一个空数组代替空对象。如果 Json.NET 希望遇到 JSON 对象,但遇到 JSON 数组,则会抛出 Cannot deserialize the current JSON array你看到的异常(exception)。

    您可能会考虑要求生成 JSON 的人修复其 JSON 输出,但与此同时,您可以使用以下转换器在反序列化对象时跳过意外数组:
    public class IgnoreUnexpectedArraysConverter<T> : IgnoreUnexpectedArraysConverterBase
    {
        public override bool CanConvert(Type objectType)
        {
            return typeof(T).IsAssignableFrom(objectType);
        }
    }
    
    public class IgnoreUnexpectedArraysConverter : IgnoreUnexpectedArraysConverterBase
    {
        readonly IContractResolver resolver;
    
        public IgnoreUnexpectedArraysConverter(IContractResolver resolver)
        {
            if (resolver == null)
                throw new ArgumentNullException();
            this.resolver = resolver;
        }
    
        public override bool CanConvert(Type objectType)
        {
            if (objectType.IsPrimitive || objectType == typeof(string))
                return false;
            return resolver.ResolveContract(objectType) is JsonObjectContract;
        }
    }
    
    public abstract class IgnoreUnexpectedArraysConverterBase : JsonConverter
    {
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            var contract = serializer.ContractResolver.ResolveContract(objectType);
            if (!(contract is JsonObjectContract))
            {
                throw new JsonSerializationException(string.Format("{0} is not a JSON object", objectType));
            }
    
            do
            {
                if (reader.TokenType == JsonToken.Null)
                    return null;
                else if (reader.TokenType == JsonToken.Comment)
                    continue;
                else if (reader.TokenType == JsonToken.StartArray)
                {
                    var array = JArray.Load(reader);
                    if (array.Count > 0)
                        throw new JsonSerializationException(string.Format("Array was not empty."));
                    return null;
                }
                else if (reader.TokenType == JsonToken.StartObject)
                {
                    // Prevent infinite recursion by using Populate()
                    existingValue = existingValue ?? contract.DefaultCreator();
                    serializer.Populate(reader, existingValue);
                    return existingValue;
                }
                else
                {
                    throw new JsonSerializationException(string.Format("Unexpected token {0}", reader.TokenType));
                }
            }
            while (reader.Read());
            throw new JsonSerializationException("Unexpected end of JSON.");
        }
    
        public override bool CanWrite { get { return false; } }
    
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    }
    

    然后,如果空数组只能出现在对象图中的一处,您可以将转换器添加到您的模型中,如下所示:
    public class Rootobject
    {
        [JsonConverter(typeof(IgnoreUnexpectedArraysConverter<Venue>))]
        public Venue venue { get; set; }
    }
    

    但是,如果如您所说,任何对象都可能被空数组替换,则可以使用非泛型 IgnoreUnexpectedArraysConverter对于所有对象类型:
    var resolver = new DefaultContractResolver(); // Cache for performance
    var settings = new JsonSerializerSettings
    {
        ContractResolver = resolver,
        Converters = { new IgnoreUnexpectedArraysConverter(resolver) },
    };
    var userInfo = JsonConvert.DeserializeObject<Rootobject>(jsonString, settings);
    

    笔记:
  • 转换器不适用于 TypeNameHandling PreserveReferencesHandling 设置。
  • 转换器假定被反序列化的对象具有默认构造函数。如果对象有 parameterized constructor您将需要创建一个硬编码转换器来分配和填充对象。
  • 如果数组不为空,转换器会抛出异常,以确保在对 JSON 结构的错误假设的情况下不会丢失数据。有时服务器会写一个对象来代替一个对象数组,当有零个、两个或多个对象时会写一个数组。如果您也处于这种情况(例如对于 "items" 数组),请参阅 How to handle both a single item and an array for the same property using JSON.net .
  • 如果您希望转换器返回默认对象而不是 null遇到数组时,修改如下:
    else if (reader.TokenType == JsonToken.StartArray)
    {
        var array = JArray.Load(reader);
        if (array.Count > 0)
            throw new JsonSerializationException(string.Format("Array was not empty."));
        return existingValue ?? contract.DefaultCreator();
    }
    

  • 工作样本.Net fiddle .

    关于json.net - 如何使用 JsonConvert.DeserializeObject 忽略空数组?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48791322/

    相关文章:

    C# Web Api - NUnit 测试 - Newtonsoft.Json.JsonSerializationException

    c# - 是否可以为 ASP.Net 中的区域设置自定义序列化程序?

    c# - 如何覆盖 newtonsoft json 中的 "Required.Always"

    c# - 使用命名空间将 Json 转换为 XML 字符串?

    c# - 我如何让 json.net 序列化从 List<T> 派生的类的成员?

    azure - response.Content.ReadAsStringAsync 不读取所有数据

    c# - 从 TD Ameritrade 反序列化 JSON

    c# - 使用 Newtonsoft 在 C# 中使用 JSON Schema 验证 JSON

    c# - 如何使用自定义 JsonConverter 仅反序列化 Json.NET 中的子对象?

    c# - 使用 JSON.NET 序列化公共(public)属性