Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom inheritance JsonConverter fails when JsonConverterAttribute is used

Tags:

c#

json.net

I am trying to deserialize derived type, and I want to use a custom property Type to distinguish between derived types.

[
  {
    "Type": "a",
    "Height": 100
  },
  {
    "Type": "b",
    "Name": "Joe"
  }
]

The solution I came to was to create a custom JsonConverter. On ReadJson I read the Type property and instantiate that type through the ToObject<T> function. Everything works fine until I use a JsonConverterAttribute. The ReadJson method loops infinitely because the attribute is applied on subtypes too.

How do I prevent this attribute from being applied to the subtypes?

[JsonConverter(typeof(TypeSerializer))]
public abstract class Base
{
    private readonly string type;

    public Base(string type)
    {
        this.type = type;
    }

    public string Type { get { return type; } }
}

public class AType : Base
{
    private readonly int height;

    public AType(int height)
        : base("a")
    {
        this.height = height;
    }

    public int Height { get { return height; } }
}

public class BType : Base
{
    private readonly string name;

    public BType(string name)
        : base("b")
    {
        this.name = name;
    }

    public string Name { get { return name; } }
}

public class TypeSerializer : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(Base);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        serializer.Serialize(writer, value);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var j = JObject.Load(reader);

        var type = j["Type"].ToObject<string>();

        if (type == "a")
            // Infinite Loop! StackOverflowException
            return j.ToObject<AType>(); 
        if (type == "b")
            return j.ToObject<BType>();

        throw new NotImplementedException(type);
    }
}

[TestFixture]
public class InheritanceSerializeTests
{
    [Test]
    public void Deserialize()
    {
        var json = @"{""Type"":""a"", ""Height"":100}";
        JObject.Parse(json).ToObject<Base>(); // Crash
    }
}
like image 947
mipi Avatar asked Sep 16 '25 22:09

mipi


1 Answers

I had a very similar problem with a project that I am currently working on: I wanted to make a custom JsonConverter and map it to my entities via attributes, but then the code got trapped in an infinite loop.

What did the trick in my case was using serializer.Populate instead of JObject.ToObject (I couldn't use .ToObject even if I wanted to; I am using version 3.5.8, in which this function does not exist). Below is my ReadJson method as an example:

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
    JContainer lJContainer = default(JContainer);

    if (reader.TokenType == JsonToken.StartObject)
    {
        lJContainer = JObject.Load(reader);
        existingValue = Convert.ChangeType(existingValue, objectType);
        existingValue = Activator.CreateInstance(objectType);

        serializer.Populate(lJContainer.CreateReader(), existingValue);
    }

    return existingValue;
}
like image 142
PedroStC Avatar answered Sep 19 '25 14:09

PedroStC