Deserialize JSON when type can be different

孤街醉人 提交于 2019-11-27 08:15:47

问题


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

personal: [] //when it is empty

or

personal: {
religion: 'Нет',
smoking: 1,
alcohol: 4
} //when not empty.

I`m deserializing most of json with JsonConvert.DeserializeObject, and this part of json with

MainObject = ((MainObject["response"].GetObject())["user"].GetObject())["personal"].GetObject();
try
{
Convert.ToByte(MainObject["political"].GetNumber();
} 
catch {}

But it makes app works slowly when it`s handling a lot of exeptions. And just now i realised that here are some more fields that might return array when empty. I just have no ideas how to make it fastly and clearly. Any suggestions?

My deserializing class (doen`t work when field is empty):

     public class User
            {
//some other fields...
                public Personal personal { get; set; }
//some other fields...
             }
    public class Personal
            {
                public byte political { get; set; }
                public string[] langs { get; set; }
                public string religion { get; set; }
                public string inspired_by { get; set; }
                public byte people_main { get; set; }
                public byte life_main { get; set; }
                public byte smoking { get; set; }
                public byte alcohol { get; set; }
            }

Another idea (doesn`t work when not empty):

public List<Personal> personal { get; set; }

回答1:


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.




回答2:


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.



来源:https://stackoverflow.com/questions/29449641/deserialize-json-when-type-can-be-different

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!