4

I have an abstract base class:

[JsonConverter(typeof(Converter))]
public abstract class TextComponent {
    ...
    public bool Bold { get; set; }
    public TextComponent[] Extra { get; set; }
    ...
}

And more classes which inherits from it. One of those classes is StringComponent:

public sealed class StringComponent : TextComponent
{
    public string Text { get; set; }

    public StringComponent(string text)
    {
        Text = text;
    }
}

Converter, which is a JsonConverter applied to TextComponent looks like this:

private sealed class Converter : JsonConverter
{
    ....

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
        JsonSerializer serializer)
    {
        var tok = JToken.Load(reader);
        switch (tok)
        {
            ...
            case JObject x:
                var dic = (IDictionary<string, JToken>) x;
                if (dic.ContainsKey("text")) return x.ToObject<StringComponent>();
                ...
            ...
        }
    }
    ...
    public override bool CanConvert(Type objectType) => objectType == typeof(TextComponent);
}

The problem:

var str = "{\"text\":\"hello world\"}";
var obj = JsonConvert.DeserializeObject<TextComponent>(str);
// this doesn't work either:
var obj = JsonConvert.DeserializeObject<StringComponent>(str);

This goes into an infinite "loop" eventually resulting in a StackOverflow, because when calling DeserializeObject<Stringcomponent> or ToObject<StringComponent>, the JsonConverter of the base class (the Converter) is used which again calls those methods. This is not the desired behavior. When serializing derived classes, they should not be using base class's JsonConverter. If you look at CanConvert method of the Converter, I'm also only allowing it for TextComponent only, not for any of it's derived classes.

So how do I fix this?

wingerse
  • 3,670
  • 1
  • 29
  • 61
  • Duplicate: [How to call JsonConvert.DeserializeObject and disable a JsonConverter applied to a base type via `[JsonConverter]`?](https://stackoverflow.com/q/45547123/3744182). – dbc Dec 19 '18 at 18:38

2 Answers2

5

You can set the converter on the sub class contract to null;

    public override bool CanWrite => false;
    public override bool CanRead => true;

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (objectType == typeof(BaseClass))
        {
            JObject item = JObject.Load(reader);
            if (item["isClass2"].Value<bool>())
            {
                return item.ToObject<ChildClass2>(serializer);
            }
            else
            {
                return item.ToObject<ChildClass1>(serializer);
            }
        }
        else
        {
            serializer.ContractResolver.ResolveContract(objectType).Converter = null;
            return serializer.Deserialize(reader, objectType);
        }
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}
1

Did you try to remove the [JsonConvert]-attribute from your base class? In my example I invoke my "custom" converter manually: https://github.com/Code-Inside/Samples/blob/b635580a4966b1b77c93a8b682389c6cf06d2da6/2015/JsonConvertIssuesWithBaseClasses/JsonConvertIssuesWithBaseClasses/Program.cs#L36-L79

Robert Muehsig
  • 5,206
  • 2
  • 29
  • 33
  • Thanks for replying :D. That + passing the serializer to ToObject does fix the problem. But I wonder why the Attribute messed up :/ – wingerse Sep 04 '17 at 14:59
  • Theory: The serializer checks for the JsonConvert attribute to select the "right" serializer and then you are stuck in a loop, which would explain the StackOverflow. – Robert Muehsig Sep 05 '17 at 07:28
  • Yeah but my JsonConverter's CanConvert method specifically allows only the base class. Moreover, the attribute is only applied to the BaseClass. So I don't see why it should follow over to derived classes. I guess it's a bug? – wingerse Sep 05 '17 at 13:55