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

7

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

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

但是我遇到了一个异常无法反序列化当前的JSON数组。如果类对象中的一个数组为空,那么就会出现这种情况。 只要有数据,一切都正常。
以下是导致反序列化器出现问题的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、NullValueHandling和MissingMemberHandling,但似乎没有一个可以防止错误的发生。
有什么办法可以反序列化JSON并忽略数据中的任何空数组? 我希望这个方法不仅适用于上面示例中的对象Venue,而且适用于所有空数组。
“新问题已发现 - 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"
                              }
                          ]
            },

具体来说,需要翻译的内容是关于IT技术的,涉及到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])才能正确反序列化。
在提供的转换器代码中,该异常发生在这一行上,带有*的标记:
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返回对象而不是数组的翻转问题吗?
谢谢, Rick
1个回答

9
您需要解决的问题并不是需要忽略空数组。如果“items”数组为空,那就没有问题了。
"items":  [],

您的问题如下。 JSON标准支持两种容器类型:
  • 数组是一个有序值集合。 数组以[(左方括号)开始,以](右方括号)结束。 值之间用,(逗号)分隔。

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

由于某种原因,服务器返回了一个空数组而不是一个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);

注意:

  • The converter does not work with the TypeNameHandling or PreserveReferencesHandling settings.

  • The converter assumes that the object being deserialized has a default constructor. It the object has a parameterized constructor you will need to create a hardcoded converter to allocate and populate the object.

  • The converter throws an exception if the array is not empty, to ensure there is no data loss in the event of incorrect assumptions about the structure of the JSON. Sometimes servers will write a single object in place of a one-object array, and an array when there are zero, two or more objects. If you are also in that situation (e.g. for the "items" array) see How to handle both a single item and an array for the same property using JSON.net.

  • If you want the converter to return a default object instead of null when encountering an array, change it as follows:

    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


dbc,你的回答太棒了,完全解决了我的问题。我采用了你建议的第二个/任意对象解决方案。我在LINQPad中使用了该代码,并对完整的JSON数据进行了运行,只有缺失的1个场馆对象现在返回为null,所有其他对象都正确地填充了。 - Rick Engle
还有一件事,转换器能否进行改进,使得当对象完全为 null 时,它可以创建一个新的实例,以便 Venue 对象与其属性一起出现在模型中,但这些属性将为 null? - Rick Engle
@RickEngle - 更新了一个选项,以便在空数组的情况下返回默认实例。 - dbc
嗨,我发现反序列化Json响应的另一个问题是,某个项突然被返回为对象而不是空数组,并在转换器中给我这个错误:sonSerializationException:无法将当前JSON对象(例如{"name":"value"})反序列化为类型“System.Collections.Generic.List`1 [System.Object]”,因为该类型需要一个JSON数组(例如[1,2,3])才能正确反序列化。我在上面的帖子中编辑了完整细节。我想知道如何扩展转换器以处理这种边缘情况的建议。 - Rick Engle
@RickEngle - 在stackoverflow的规定是[每个帖子只提一个问题] (https://meta.stackexchange.com/q/222735/344280),因此您应该提出另一个问题。但先看一下[如何使用JSON.net处理同一属性的单个项目和数组] (https://dev59.com/12Mk5IYBdhLWcg3wxAhM)。 - dbc
抱歉DBC,我担心会出现重复的帖子。实际上,我找到了一个更简单的解决方案。我一直在使用json2csharp.com从json中创建C#类,但在这种情况下,对象模型未完全形成,因为没有任何示例数据,所以导致崩溃的属性是:public List <object> venue_details {get; set;},一旦我有了数据并重新运行了类生成器,就得到了这个:public VenueDetails venue_details {get; set;}。一旦我有了这个新属性和VenueDetails类,错误就消失了! - Rick Engle

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接