Deserialize JSON when a value can be an object or an empty array

后端 未结 2 497
生来不讨喜
生来不讨喜 2020-12-04 03:05

I`m working with VK API. Sometimes server can return empty array instead of object, for example:

personal: [] //when it is empty

or

相关标签:
2条回答
  • 2020-12-04 03:27

    Instead of using try catch to switch between two possibilities, just check the first character. If it is '[', it's null, if it is '{' then you deserialize.

    EDIT:

    Now considering that the object is not the whole of the JSON, it gives me an idea: We had a similar problem with API returning inconsistent JSON serializations. In the end, we used NewtonSoft's ServiceStack.Text library (available from NuGet). We serialized to JToken objects instead of the target class. Then we processed the JToken structures to do piecemeal deserialization.

    0 讨论(0)
  • 2020-12-04 03:33

    You could make a JsonConverter like the following, that looks for either an object of a specified type, or an empty array. If an object, it deserializes that object. If an empty array, it returns null:

    public class JsonSingleOrEmptyArrayConverter<T> : JsonConverter where T : class
    {
        public override bool CanConvert(Type objectType)
        {
            return typeof(T).IsAssignableFrom(objectType);
        }
    
        public override bool CanWrite { get { return false; } }
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            var contract = serializer.ContractResolver.ResolveContract(objectType);
            if (!(contract is Newtonsoft.Json.Serialization.JsonObjectContract || contract is Newtonsoft.Json.Serialization.JsonDictionaryContract))
            {
                throw new JsonSerializationException(string.Format("Unsupported objectType {0} at {1}.", objectType, reader.Path));
            }
    
            switch (reader.SkipComments().TokenType)
            {
                case JsonToken.StartArray:
                    {
                        int count = 0;
                        while (reader.Read())
                        {
                            switch (reader.TokenType)
                            {
                                case JsonToken.Comment:
                                    break;
                                case JsonToken.EndArray:
                                    return existingValue;
                                default:
                                    {
                                        count++;
                                        if (count > 1)
                                            throw new JsonSerializationException(string.Format("Too many objects at path {0}.", reader.Path));
                                        existingValue = existingValue ?? contract.DefaultCreator();
                                        serializer.Populate(reader, existingValue);
                                    }
                                    break;
                            }
                        }
                        // Should not come here.
                        throw new JsonSerializationException(string.Format("Unclosed array at path {0}.", reader.Path));
                    }
    
                case JsonToken.Null:
                    return null;
    
                case JsonToken.StartObject:
                    existingValue = existingValue ?? contract.DefaultCreator();
                    serializer.Populate(reader, existingValue);
                    return existingValue;
    
                default:
                    throw new InvalidOperationException("Unexpected token type " + reader.TokenType.ToString());
            }
        }
    
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    }
    
    public static partial class JsonExtensions
    {
        public static JsonReader SkipComments(this JsonReader reader)
        {
            while (reader.TokenType == JsonToken.Comment && reader.Read())
                ;
            return reader;
        }
    }
    

    Then use it like:

    public class User
    {
        //some other fields...
        [JsonConverter(typeof(JsonSingleOrEmptyArrayConverter<Personal>))]
        public Personal personal { get; set; }
        //some other fields...
    }
    

    You should now be able to deserialize a user into your User class.

    Notes:

    • The converter can be applied via attributes or in JsonSerializerSettings.Converters.

    • The converter isn't designed to work with simple types such as strings, it's designed for classes that map to a JSON object. That's because it uses JsonSerializer.Populate() to avoid an infinite recursion during reading.

    Working sample .Net fiddles here and here.

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