3

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; }
dbc
  • 104,963
  • 20
  • 228
  • 340
nikita_97_10
  • 335
  • 1
  • 4
  • 11
  • 1
    Try adapting SingleOrArrayConverter from here: https://stackoverflow.com/questions/18994685/how-to-handle-both-a-single-item-and-an-array-for-the-same-property-using-json-n – dbc Apr 04 '15 at 18:43
  • Thanks for a good idea, it is something I was looking for, hope I can realise it... – nikita_97_10 Apr 04 '15 at 18:55

2 Answers2

3

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.

dbc
  • 104,963
  • 20
  • 228
  • 340
  • CS1061 'Type' does not contain a definition for 'IsAssignableFrom' and no extension method 'IsAssignableFrom' accepting a first argument of type 'Type' could be found (are you missing a using directive or an assembly reference?) Here: public override bool CanConvert(Type objectType) { return typeof(T).IsAssignableFrom(objectType); } – nikita_97_10 Apr 04 '15 at 20:04
  • @nikita_97_10 - `objectType == typeof(T))` should be fine as long as you're not subclassing your classes. – dbc Apr 04 '15 at 21:24
  • @nikita_97_10 - [`Type.IsAssignableFrom`](https://msdn.microsoft.com/en-us/library/system.type.isassignablefrom%28v=vs.110%29.aspx) is a standard method on type, I think it's present in most frameworks. – dbc Apr 04 '15 at 21:27
  • 1
    but in windows phone 8.1 it doesn`t exists, as I think – nikita_97_10 Apr 05 '15 at 07:12
0

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.

Boluc Papuccuoglu
  • 2,318
  • 1
  • 14
  • 24
  • Interesting idea, but the problem is that I need to do it in every situation, and i have a lot of different json files. It would be better to realize that in class itself. Also, it is only the little part of json, and I`m not sure can I check It fast... – nikita_97_10 Apr 04 '15 at 18:14
  • I see, I was under the impression that this was the whole JSON file – Boluc Papuccuoglu Apr 04 '15 at 18:44
  • 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. – Boluc Papuccuoglu Apr 04 '15 at 18:48
  • Do you know, are there some other types of objects that might be deserialized larer? – nikita_97_10 Apr 04 '15 at 18:50
  • Yes, JToken. In your case if the object is empty, JToken will also be a JArray. If it's not, it will be a JObject (JToken is the base class for JArray and JObject if I recall correctly). You can test for that using the "is" operator and deserialize accordingly. – Boluc Papuccuoglu Apr 04 '15 at 18:54
  • If this https://stackoverflow.com/questions/18994685/how-to-handle-both-a-single-item-and-an-array-for-the-same-property-using-json-n will not work, I'll try to make it as you advised – nikita_97_10 Apr 04 '15 at 19:00
  • I would advise you to try that solution too. It is much more elegant and maintainable than my suggestion. – Boluc Papuccuoglu Apr 04 '15 at 19:03