Custom JsonConverter causes a stack overflow when base class is not abstract

38 views Asked by At

I'm using System.Text.Json to deserialize objects of different types that all inherit from the same abstract base class

[JsonConverter(typeof(MyBaseClassConverter))]
internal abstract class MyBaseClass {
  public int SomeBaseClassInt { get;set; }
}

internal class MyDerivedClassA : MyBaseClass {
  public int SomeClassAOnlyInt { get; set; }
}

internal class MyDerivedClassB : MyBaseClass {
  public int SomeClassBOnlyInt { get;set; }
}

I have a custom JsonConverter like this. I use a copy of the Utf8JsonReader to find my type discriminator, and then the original Utf8JsonReader (which is still positioned at the start) to do the actual deserialization.

internal class MyBaseClassConverter : JsonConverter<MyBaseClass> {
  public override bool CanConvert(Type type) {
    return typeof(MyBaseClass).IsAssignableFrom(type);
  }

  public override MyBaseClass Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
    Utf8JsonReader readerClone = reader;
    while (readerClone.Read()) {
      if (readerClone.TokenType == JsonTokenType.PropertyName) {
        if (readerClone.GetString() == "ObjectType") {
          if (!readerClone.Read()) { throw new JsonException(); }
          if (readerClone.TokenType == JsonTokenType.String) {
            string objectType = readerClone.GetString();
            if (objectType == "TypeA") {
              return JsonSerializer.Deserialize<MyDerivedClassA>(ref reader, options);
            } else if (objectType == "TypeB") {
              return JsonSerializer.Deserialize<MyDerivedClassB>(ref reader, options);
            } else {
              throw new JsonException($"Invalid value {objectType} for ObjectType");
            }
          }
        }
      }
      readerClone.Skip();
    }

    throw new JsonException("ObjectType is required");
  }

  public override void Write(Utf8JsonWriter writer, MyBaseClass value, JsonSerializerOptions options) {
    if (value is MyDerivedClassA classAObject) {
      JsonSerializer.Serialize(writer, classAObject, options);
    } else if (value is MyDerivedClassB classBObject) {
      JsonSerializer.Serialize(writer, classBObject, options);
    } else {
      throw new JsonException("Unsupported object type");
    }
  }
}

So given some JSON like this

[
  {
    "SomeBaseClassInt": 12,
    "SomeClassAOnlyInt": 6,
    "ObjectType": "TypeA"
  },
  {
    "SomeBaseClassInt": 7,
    "SomeClassAOnlyInt": 2,
    "ObjectType": "TypeA"
  },
  {
    "SomeBaseClassInt": 9,
    "SomeClassBOnlyInt": 1,
    "ObjectType": "TypeB"
  }
]

I can do

List<MyBaseClass> listOfObjects = JsonSerializer.Deserialize<List<MyBaseClass>>(incomingJson);

And it works exactly as I want.

But now I'm trying to do the same thing with a structure where the base class isn't abstract.

[JsonConverter(typeof(MyBaseClassConverter))]
internal class MyBaseClass {
  public int SomeBaseClassInt { get;set; }
}

internal class MyDerivedClassA : MyBaseClass {
  public int SomeClassAOnlyInt { get; set; }
}

internal class MyDerivedClassB : MyBaseClass {
  public int SomeClassBOnlyInt { get;set; }
}

I've altered my custom JsonConverter to handle objects of the base class

string objectType = readerClone.GetString();
if (objectType == "TypeBase") {
  return JsonSerializer.Deserialize<MyBaseClass>(ref reader, options);
} else if(objectType == "TypeA") {
  return JsonSerializer.Deserialize<MyDerivedClassA>(ref reader, options);
} else if (objectType == "TypeB") {
  return JsonSerializer.Deserialize<MyDerivedClassB>(ref reader, options);
} else {
  throw new JsonException($"Invalid value {objectType} for ObjectType");
}

So now I would hope to be able to deal with something like this

[
  {
    "SomeBaseClassInt": 12,
    "ObjectType": "TypeBase"
  },
  {
    "SomeBaseClassInt": 7,
    "SomeClassAOnlyInt": 2,
    "ObjectType": "TypeA"
  },
  {
    "SomeBaseClassInt": 9,
    "SomeClassBOnlyInt": 1,
    "ObjectType": "TypeB"
  }
]

But of course, when I run it, I get a stack overflow because when it tries to deserialize the base class object, it just calls itself infinitely.

Can anybody think of a way around this? Where I do

return JsonSerializer.Deserialize<MyBaseClass>(ref reader, options);

in the custom JsonConverter, is there a way of telling it not to use the custom converter this time?

I've tried using the .NET 7 way of doing it with JsonPolymorphic and JsonDerivedType attributes and that would be perfect except for the requirement that the type discriminator is always at the beginning of the JSON object. I can't guarantee that, so I don't think I can use it.

1

There are 1 answers

0
Tanveer Badar On

Your problem might be the default implementation of bool CanConvert(Type typeToConvert), which returns true if typeToConvert == typeof(T).

You can try overriding it to return false for the base type and true for others. Or, perhaps provide separate converters for the derived types now that your base is no longer abstract.