Skip to content

Instantly share code, notes, and snippets.

@chrisoldwood
Last active February 17, 2023 23:16
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chrisoldwood/b604d69543a5fe5896a94409058c7a95 to your computer and use it in GitHub Desktop.
Save chrisoldwood/b604d69543a5fe5896a94409058c7a95 to your computer and use it in GitHub Desktop.
Example showing infinite loop deserialising polymorphic types with a custom JsonConverter.
namespace JsonTests
{
public class CustomJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(BaseType).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
JObject item = JObject.Load(reader);
#if USE_TO_OBJECT
var @object = item.ToObject<DerivedType>();
return @object;
#else // USE_POPULATE
var @object = new DerivedType();
serializer.Populate(item.CreateReader(), @object);
return @object;
#endif
}
public override bool CanWrite => false;
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
}
namespace JsonTests
{
[JsonConverter(typeof(CustomJsonConverter))]
public abstract class BaseType
{
public string Value { get; set; }
}
public class DerivedType : BaseType
{
public string DerivedValue { get; set; }
}
public class Container
{
public List<BaseType> Objects { get; set; }
}
}
namespace JsonTests
{
[TestFixture]
public class Tests
{
[Test]
public void Test()
{
const string json =
"{ 'Objects': [" +
" { 'Value': 'test value' } " +
"] }";
#if USE_TO_OBJECT
Assert.That(() => JsonConvert.DeserializeObject<Container>(json),
Throws.InstanceOf<StackOverflowException>());
#else // USE_POPULATE
Assert.That(() => JsonConvert.DeserializeObject<Container>(json),
Throws.Nothing);
#endif
}
}
}
@chrisoldwood
Copy link
Author

chrisoldwood commented Jun 4, 2017

This Gist shows two techniques listed on Stack Overflow on how to handle the deserialisation of polymorphic types with Newtonsoft's JSON.Net. The use of ToObject above in ReadJson results in an infinite loop and subsequently a stack overflow, whereas the use of Populate works as desired. The ToObject approach is the one described in Deserializing polymorphic json classes without type information using json.net whereas the other one comes from Custom inheritance JsonConverter fails when JsonConverterAttribute is used [*]. A third example from Stack Overflow that suggests the latter approach is the preferred one is Polymorphic JSON Deserialization failing using Json.Net.

To invoke the stack overflow behaviour in the example above use #define USE_TO_OBJECT, whereas not defining it will use the working approach. I don't understand why the technique suggested in Deserializing polymorphic json classes without type information using json.net doesn't work for me here. I guess I'm doing something wrong, or subtly different, as otherwise the answer would surely be down-voted? Or perhaps JSON.Net has acquired a bug (or had one fixed) since the original answer was given?

Either way if you know what's going on I'd sure appreciate a comment explaining what I'm missing here :o).

[*] In another example (at work) I managed to get it working when manually adding the custom converter to the JsonSerializerSettings object but also got a failure when using the attribute based approach. In the above example it fails in both scenarios.

@xtravar
Copy link

xtravar commented Sep 18, 2017

Thanks for the info. However, the stack overflow answers you cite did not use the attribute on the base type. They inject the converters upon serializer creation so that they do not affect the subtypes. And maybe it's important to only check the base type in CanConvert, too, depending on the use case.

@Melechous
Copy link

Thanks Chris, this was very helpful. I had an example where I was using the toObject method in JsonSerializerSettings and it worked so I was surprised when my new code used that method and was causing the stack overflow exception.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment