Search code examples
c#jsonlitjson

Deserializing JSON with different object types


I have a json with structure like this:

{
   "result":
   [
      {"a":{"b":1,"c":2}},
      {"a":{"b":1,"c":2}},
      {"a":{"b":1,"c":2}},
      {"a":[]}
   ]
}

So I created class structure to parse this into C# objects:

public class JsonTest
{
   public JsonTestResult[] result;
}

public class JsonTestResult
{
   public JsonTestResultValue a;
}

public class JsonTestResultValue
{
   public int b;
   public int c;
}

when trying to parse this via LitJson I get an error:

Type JsonTestResultValue can't act as an array

the problem is in this part of the json: {"a":[]}, there must be {} instead of [], because it's really an object, not an array.

Now I'm stuck here and can't understand which type that I must use for this json property - array or any object type is unsuitable. All that I have on my mind now is to replace braces (with Regex of simple String.Replace), but I'm sure, there must be more adequate way to deserialize this.


Solution

  • What you need to do is to create a custom converter and use inheritance.

    To start with I've changed so that there are two subclasses:

    public class JsonTestObject
    {
    
    }
    
    public class JsonTestResultValue : JsonTestObject
    {
        public int b;
        public int c;
    }
    
    public class JsonTestResultArray : JsonTestObject
    {
        public JArray Array { get; set; }
    
        public JsonTestResultArray(JArray array)
        {
            Array = array;
        }
    }
    

    Which are used in the main structure:

    public class JsonTest
    {
        public JsonTestResult[] result;
    }
    
    public class JsonTestResult
    {
        public JsonTestObject a;
    }
    

    We then need to be able to identify which of the subclasses to use. To do that we can check if the start token is for an object or for an array. that is done inside a JsonConverter:

    public class JsonTestConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return typeof(JsonTestObject).IsAssignableFrom(objectType);
        }
    
        public override object ReadJson(JsonReader reader,
            Type objectType, object existingValue, JsonSerializer serializer)
        {
            //Is it an array?
            var token = reader.TokenType;
            if (token == JsonToken.StartArray)
            {
                var array = JArray.Load(reader);
                return new JsonTestResultArray(array);
            }
    
            var item = JObject.Load(reader);
            return item.ToObject<JsonTestResultValue>();
        }
    
        public override bool CanWrite
        {
            get { return false; }
        }
    
        public override void WriteJson(JsonWriter writer,
            object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    }
    

    Finally you have to specify the converter when deserializing:

    class Program
    {
        static void Main(string[] args)
        {
            var str = @"{
            ""result"":
            [
               {""a"":{""b"":1,""c"":2}},
               {""a"":{""b"":1,""c"":2}},
               {""a"":{""b"":1,""c"":2}},
               {""a"":[]}
            ]
        }";
            var deserializedObject = JsonConvert.DeserializeObject<JsonTest>(str, new JsonTestConverter());
    
        }
    }
    

    You probably want to change the JArray against your real array type in the JsonTestResultArray.