如何完全防御JsonConvert当对象类属性不期望数据时反序列化对象错误?

时间:2018-05-02 16:43:47

标签: c# json json-deserialization

我经常使用http://json2csharp.com为Json数据响应创建C#类,我经常发现,如果我插入类生成器的Json的特定快照缺少数据,那么我不会#39; t最终得到正确的类属性来处理最终导致未处理的反序列化错误的数据。 这是一个例子:

    "toasts": {
        "total_count": 1,
        "count": 1,
        "auth_toast": false,
        "items": [
            {
                "uid": 3250810,
                "user": {
                    "uid": 3250810,
                    "user_name": "jdoe",
                    "first_name": "Jane",
                    "last_name": "Doe",
                    "bio": "",
                    "location": "",
                    "relationship": "friends",
                    "user_avatar": "",
                    "account_type": "user",
                    "venue_details": [
                    ],
                    "brewery_details": [
                    ]
                },
                "like_id": 488764809,
                "like_owner": false,
                "created_at": "Fri, 16 Mar 2018 20:35:44 +0000"
            }
        ]
    },

在该示例中," venue_details下没有数据:"所以没有发现任何属性,生成的C#类看起来像这样:

    public class ViewCheckinID_Toasts
    {
        public int total_count { get; set; }
        public int count { get; set; }
        public bool auth_toast { get; set; }
        public List<ViewCheckinID_Item2> items { get; set; }
    }


    public class ViewCheckinID_Item2
    {
        public int uid { get; set; }
        public ViewCheckinID_User2 user { get; set; }
        public int like_id { get; set; }
        public bool like_owner { get; set; }
        public string created_at { get; set; }
    }

public class ViewCheckinID_User2
{
    public int uid { get; set; }
    public string user_name { get; set; }
    public string first_name { get; set; }
    public string last_name { get; set; }
    public string bio { get; set; }
    public string location { get; set; }
    public string relationship { get; set; }
    public string user_avatar { get; set; }
    public string account_type { get; set; }
    public object[] venue_details { get; set; }
    public object[] brewery_details { get; set; }
    public string user_link { get; set; }
}

请注意,在课程结束时,生成的属性为: public object [] venue_details {get;组; }

如果Json数据突然最终在venue_details中有底层数据,会发生什么,我得到一个例外。这是Json的一个片段:

                        "venue_details":  {
                            "venue_id":  329118
                        },

所以现在它不是一个数组,但实际上是一个字符串属性。最终导致反序列化错误:

JsonSerializationException:无法将当前JSON对象(例如{&#34; name&#34;:&#34; value&#34;})反序列化为类型System.Object []&#39;因为该类型需要JSON数组(例如[1,2,3])才能正确反序列化。 要修复此错误,请将JSON更改为JSON数组(例如[1,2,3])或更改反序列化类型,使其成为普通的.NET类型(例如,不是像整数这样的基本类型,而不是类似的集合类型可以从JSON对象反序列化的数组或List。 JsonObjectAttribute也可以添加到类型中以强制它从JSON对象反序列化。 路径&#39; response.checkin.toasts.items [0] .user.venue_details.venue_id&#39;,第143行,第20位。

如果我再次使用新的Json并通过C#类生成器运行它,那么我将获得这个已修复的属性,然后错误消失:

    public class ViewCheckinID_User2
    {
        public int uid { get; set; }
        public string user_name { get; set; }
        public string first_name { get; set; }
        public string last_name { get; set; }
        public string bio { get; set; }
        public string location { get; set; }
        public string relationship { get; set; }
        public string user_avatar { get; set; }
        public string account_type { get; set; }
        public ViewCheckinID_VenueDetails venue_details { get; set; }
        public object[] brewery_details { get; set; }
        public string user_link { get; set; }
    }

    public class ViewCheckinID_VenueDetails
    {
        public int venue_id { get; set; }
    }

我想做的是能够抵御此错误。我一直在使用这些帮助程序类进行反序列化,但它没有处理这种情况:

    string strFileName = @"C:\Users\rick\Documents\files\dev\Explorer\DataModels\ricke_Checkin_View_CheckinID.json";


    var resolver = new DefaultContractResolver(); // Cache for performance
    var Serializersettings = new JsonSerializerSettings
    {
        ContractResolver = resolver,
        Converters = { new IgnoreUnexpectedArraysConverter(resolver) },
    };
    ViewCheckinID_RootObject checkinInfo = JsonConvert.DeserializeObject<ViewCheckinID_RootObject>(File.ReadAllText(strFileName), Serializersettings);

    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 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;
                }
                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 SingleValueArrayConverter<T> : JsonConverter
    {
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            object retVal = new Object();
            if (reader.TokenType == JsonToken.StartObject)
            {
                T instance = (T)serializer.Deserialize(reader, typeof(T));
                retVal = new List<T>() { instance };
            } else if (reader.TokenType == JsonToken.StartArray) {
                retVal = serializer.Deserialize(reader, objectType);
            }
            return retVal;
        }

        public override bool CanConvert(Type objectType)
        {
            return true;
        }
    }

如果在帮助程序类IgnoreUnexpectedArraysConverterBase中看到,以***开头的行是发生反序列化异常的位置。

所以现在我把所有这些信息都列出来了,我的问题是:

  • 如何为类属性为
    的情况添加处理程序 期望一个Object,因为在生成
    时没有找到数据 班级突然有数据吗?

  • 有没有办法添加一个catch-all处理程序,以便如果有任何异常
    试图反序列化一个只是简单的属性 忽略并且占位符值如null或空字符串为
    返回?

我似乎不断陷入无休止的情况,获得新的Json响应,类定义没有正确预期,我的应用程序崩溃了另一个deserializion错误。我希望反序列化具有足够的弹性来抵御任何类型的意外数据,如果遇到问题,优雅地失败并让应用程序继续运行。

1 个答案:

答案 0 :(得分:0)

我查看了您的链接json2chsarp。如果您已启用&#34;使所有属性可选&#34;你可能不会遇到这个问题。我对Azure和Google的OCR JSON字符串也有同样的看法。我不得不对我的代码进行大量重写。

enter image description here

格式化JSON。

{
   "toasts":[
      {
         "total_count":1,
         "count":1,
         "auth_toast":false,
         "items":[
            {
               "uid":3250810,
               "user":{
                  "uid":3250810,
                  "user_name":"jdoe",
                  "first_name":"Jane",
                  "last_name":"Doe",
                  "bio":"",
                  "location":"",
                  "relationship":"friends",
                  "user_avatar":"",
                  "account_type":"user",
                  "venue_details":[

                  ],
                  "brewery_details":[

                  ]
               },
               "like_id":488764809,
               "like_owner":false,
               "created_at":"Fri, 16 Mar 2018 20:35:44 +0000"
            }
         ]
      }
   ]
}

从quicktype自动生成代码。

// To parse this JSON data, add NuGet 'Newtonsoft.Json' then do:
//
//    using QuickType;
//
//    var welcome = Welcome.FromJson(jsonString);

namespace QuickType
{
    using System;
    using System.Collections.Generic;

    using System.Globalization;
    using Newtonsoft.Json;
    using Newtonsoft.Json.Converters;

    public partial class Welcome
    {
        [JsonProperty("toasts", NullValueHandling = NullValueHandling.Ignore)]
        public Toast[] Toasts { get; set; }
    }

    public partial class Toast
    {
        [JsonProperty("total_count", NullValueHandling = NullValueHandling.Ignore)]
        public long? TotalCount { get; set; }

        [JsonProperty("count", NullValueHandling = NullValueHandling.Ignore)]
        public long? Count { get; set; }

        [JsonProperty("auth_toast", NullValueHandling = NullValueHandling.Ignore)]
        public bool? AuthToast { get; set; }

        [JsonProperty("items", NullValueHandling = NullValueHandling.Ignore)]
        public Item[] Items { get; set; }
    }

    public partial class Item
    {
        [JsonProperty("uid", NullValueHandling = NullValueHandling.Ignore)]
        public long? Uid { get; set; }

        [JsonProperty("user", NullValueHandling = NullValueHandling.Ignore)]
        public User User { get; set; }

        [JsonProperty("like_id", NullValueHandling = NullValueHandling.Ignore)]
        public long? LikeId { get; set; }

        [JsonProperty("like_owner", NullValueHandling = NullValueHandling.Ignore)]
        public bool? LikeOwner { get; set; }

        [JsonProperty("created_at", NullValueHandling = NullValueHandling.Ignore)]
        public string CreatedAt { get; set; }
    }

    public partial class User
    {
        [JsonProperty("uid", NullValueHandling = NullValueHandling.Ignore)]
        public long? Uid { get; set; }

        [JsonProperty("user_name", NullValueHandling = NullValueHandling.Ignore)]
        public string UserName { get; set; }

        [JsonProperty("first_name", NullValueHandling = NullValueHandling.Ignore)]
        public string FirstName { get; set; }

        [JsonProperty("last_name", NullValueHandling = NullValueHandling.Ignore)]
        public string LastName { get; set; }

        [JsonProperty("bio", NullValueHandling = NullValueHandling.Ignore)]
        public string Bio { get; set; }

        [JsonProperty("location", NullValueHandling = NullValueHandling.Ignore)]
        public string Location { get; set; }

        [JsonProperty("relationship", NullValueHandling = NullValueHandling.Ignore)]
        public string Relationship { get; set; }

        [JsonProperty("user_avatar", NullValueHandling = NullValueHandling.Ignore)]
        public string UserAvatar { get; set; }

        [JsonProperty("account_type", NullValueHandling = NullValueHandling.Ignore)]
        public string AccountType { get; set; }

        [JsonProperty("venue_details", NullValueHandling = NullValueHandling.Ignore)]
        public object[] VenueDetails { get; set; }

        [JsonProperty("brewery_details", NullValueHandling = NullValueHandling.Ignore)]
        public object[] BreweryDetails { get; set; }
    }

    public partial class Welcome
    {
        public static Welcome FromJson(string json) => JsonConvert.DeserializeObject<Welcome>(json, QuickType.Converter.Settings);
    }

    public static class Serialize
    {
        public static string ToJson(this Welcome self) => JsonConvert.SerializeObject(self, QuickType.Converter.Settings);
    }

    internal static class Converter
    {
        public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
        {
            MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
            DateParseHandling = DateParseHandling.None,
            Converters = { 
                new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal }
            },
        };
    }
}