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

时间:2018-02-14 15:59:10

标签: json.net

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

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

但我得到例外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选项,包括DefaultValueHandlingNullValueHandlingMissingMemberHandling,但似乎都没有阻止错误。

知道如何反序列化JSON并忽略数据中的任何空数组吗?我希望这能处理任何空数组,而不仅仅是对象Venue的上述示例。

  
    

发现新问题 - 03/17/2018&lt;&lt;

  

嗨,下面的转换器一直工作得很好,但我得到的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最终看起来像一个对象而不是一个数组。这最终会给出这个例外:

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返回对象而不是数组之间添加这些额外的处理来解释这样的翻转?

谢谢,  瑞克

1 个答案:

答案 0 :(得分:0)

您的问题不是您需要忽略空数组。如果"items"数组为空,则没有问题:

"items":  [],

相反,您的问题如下。 JSON standard支持两种类型的容器:

  • 数组,是有序的值集合。数组以[(左括号)开头,以](右括号)结尾。值以,(逗号)分隔。

  • 对象,是一组无序的名称/值对。对象以{(左大括号)开头,以}(右大括号)结束。

由于某种原因,服务器返回空数组来代替 null对象。如果Json.NET希望遇到一个JSON对象,而是遇到一个JSON数组,它将抛出你看到的Cannot deserialize the current JSON array异常。

您可能会考虑询问生成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);

注意:

  • 转换器无法使用TypeNameHandlingPreserveReferencesHandling设置。

  • 转换器假定反序列化的对象具有默认构造函数。对象具有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