Handling JSON response which sometimes doesn't have array

前端 未结 1 1287
无人及你
无人及你 2021-01-27 18:17

I am consuming some JSON in a C# console application and for some of the data, there is an array of options. Example JSON:

{
    \"FIELD_NAME\": \"Survey\",
             


        
相关标签:
1条回答
  • 2021-01-27 18:59

    Json.NET will throw an exception when the expected JSON value type (array, collection or primitive) does not match the observed value type. Since, in the case of your List<Option> OPTIONS, you want unexpected value types to be skipped, you will need to create a custom JsonConverter such as the following:

    public class TolerantCollectionConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            if (objectType.IsPrimitive || objectType == typeof(string) || objectType.IsArray)
                return false;
            return objectType.GetCollectionItemTypes().Count() == 1;
        }
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            if (reader.TokenType == JsonToken.Null)
            {
                return null;
            }
            else if (reader.TokenType == JsonToken.StartArray)
            {
                existingValue = existingValue ?? serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
                serializer.Populate(reader, existingValue);
                return existingValue;
            }
            else
            {
                reader.Skip();
                return existingValue;
            }
        }
    
        public override bool CanWrite { get { return false; } }
    
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    }
    
    public static class TypeExtensions
    {
        /// <summary>
        /// Return all interfaces implemented by the incoming type as well as the type itself if it is an interface.
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static IEnumerable<Type> GetInterfacesAndSelf(this Type type)
        {
            if (type == null)
                throw new ArgumentNullException();
            if (type.IsInterface)
                return new[] { type }.Concat(type.GetInterfaces());
            else
                return type.GetInterfaces();
        }
    
        public static IEnumerable<Type> GetCollectionItemTypes(this Type type)
        {
            foreach (Type intType in type.GetInterfacesAndSelf())
            {
                if (intType.IsGenericType
                    && intType.GetGenericTypeDefinition() == typeof(ICollection<>))
                {
                    yield return intType.GetGenericArguments()[0];
                }
            }
        }
    }
    

    Then apply it to Field as follows:

    public class Field
    {
        public string FIELD_NAME { get; set; }
        public string VALUE { get; set; }
        public int FIELD_ID { get; set; }
    
        [JsonConverter(typeof(TolerantCollectionConverter))]
        public List<Option> OPTIONS { get; set; }
    }
    

    Or use it for all collections via JsonSerializerSettings:

    var settings = new JsonSerializerSettings
    {
        Converters = { new TolerantCollectionConverter() },
    };
    var obj = JsonConvert.DeserializeObject<Field>(stringTest, settings);
    

    Notes:

    • The converter only works for collections that are writable, since it allocates the collection first and then populates it. For read-only collections or arrays you need to populate a List<T> first then allocate the read-only collection or array from it.

    • My assumption here is that you want to ignore an empty object when an array value is expected. If instead you want to deserialize the object into a collection item then add that to the returned collection you could use SingleOrArrayConverter<T> from How to handle both a single item and an array for the same property using JSON.net.

    • The root JSON container shown in your question is an object -- an unordered set of name/value pairs that begins with { and ends with } -- rather than an array. Thus you need to deserialize it as a Field not a List<Field>.

    Sample fiddle.

    0 讨论(0)
提交回复
热议问题