Search code examples
json.netjson-deserialization

Cannot Deserialize Recursively a Class Hierarchy Using JSON.NET


I have a class hierarchy like this:

class Rule { }
class Condition { List<Rule> Rules { get; set; } }

Forget about the remaining properties. I need to deserialize from a JSON string, using a custom JsonConverter. The problem is, I have code for each specific case, but I cannot have it ran recursively, for taking care of the Rules property, each of its elements can be a Condition too. My code looks like this (ReadJson method):

var jo = JObject.Load(reader);
Rule rule = null;

if (jo["condition"] == null)
{
    rule = new Rule();
    //fill the properties for rule
}
else
{
    rule = new Condition();
    //I now want the converter to go through all the values in jo["rules"] and turn them into Rules or Conditions
}

What is the best way to achieve this? I tried to get the JSON for the remaining part, if the object is found to be a Condition:

var json = jo.GetValue("rule").ToString();

But I cannot deserialize it like this, it throws an exception:

var rules = JsonConvert.DeserializeObject<Rule[]>(json, this);

The exception is: JsonReaderException : Error reading JObject from JsonReader. Current JsonReader item is not an object: StartArray. Path '', line 1, position 1.

Any ideas?


Solution

  • You're not far from having it working. After you instantiate correct type of object based on the presence or absence of the condition property in the JSON, you can populate the instance using the serializer.Populate method. This should take care of the recursion automatically. You do need to pass a new JsonReader instance to Populate, which you can create using jo.CreateReader().

    Here is what the converter should look like:

    public class RuleConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return typeof(Rule).IsAssignableFrom(objectType);
        }
    
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            var jo = JObject.Load(reader);
            Rule rule = null;
    
            if (jo["condition"] == null)
            {
                rule = new Rule();
            }
            else
            {
                rule = new Condition();
            }
    
            serializer.Populate(jo.CreateReader(), rule);
            return rule;
        }
    
        public override bool CanWrite
        {
            get { return false; }
        }
    
        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            throw new NotImplementedException();
        }
    }
    

    Here is a working example: https://dotnetfiddle.net/SHctMo