0

I'm having trouble de-serializing JSON Polymorhpically. Here is a minimal example:

I have an Enum defining the type of JSON:

public enum BlockType { T1, T2 } 

The base class and the child class (and the container):

public class Container { required public List<BaseType> ListOfItems { get; set; } } [JsonPolymorphic(TypeDiscriminatorPropertyName = nameof(Type))] [JsonDerivedType(typeof(Type1), nameof(BlockType.T1))] [JsonDerivedType(typeof(Type2), nameof(BlockType.T2))] public class BaseType { [JsonConverter(typeof(JsonStringEnumConverter))] required public BlockType Type { get; set; } } public class Type1 : BaseType { required public int Value1 { get; set; } } public class Type2 : BaseType { required public int Value2 { get; set; } } 

I have a input JSON:

var jsonBody = """ { "ListOfItems": [ { "type": "T1", "Value1": 10 }, { "type": "T2", "Value2": 11 } ] } """; 

And call the serializer:

var deserializeOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true, WriteIndented = true, }; var decoded = JsonSerializer.Deserialize<Container>(jsonBody, deserializeOptions); 

The serializer does NOT returns any errors what so ever. However, when I checked the returned type and the list, Container.ListOfItems, every items in it is the BaseType, instead of the derived Type1 or Type2. In fact, if I cast the object:

foreach (BaseType o in decoded.ListOfItems) { if (o.Type == BlockType.T1) { Type1 obj = (Type1)o; } } 

I would instead get an InvalidCastException: Cannot convert BaseType to Type1.

Is there a solution to this problem?

2
  • It looks like you have Type2 twice in the attributes Commented Apr 15, 2024 at 22:55
  • @DanielA.White it was a typo, its now fixed. Commented Apr 16, 2024 at 0:32

3 Answers 3

3

You have some errors in your code and attributes. Basically deserialization can work fine without custom JsonConverter.

I can suggest to you review 2 solutions.

Solution 1:

Please review your code again...

You try to use one property (type) and for detect instance type and for property. You need to use different JSON properties for this purpose.

You can change JsonPolymorphic attribute to this:

[JsonPolymorphic(TypeDiscriminatorPropertyName = "__type")] 

In this case your JSON string must see like that:

{ "ListOfItems": [ { "__type": "T1", "Type": "T1", "Value1": 10 }, { "__type": "T2", "Type": "T2", "Value2": 11 } ] } 

In this case by value "__type" deserializator will create proper instance of your class and will populate property Type correctly (to your enum value).

Solution 2:

Probably you want to redesign your classes inheritance. It seems class BaseType must be abstract. In this case you can override property Type in your inherited classes.

Please review code snippet there:

[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")] [JsonDerivedType(typeof(Type1), nameof(BlockType.T1))] [JsonDerivedType(typeof(Type2), nameof(BlockType.T2))] public abstract class BaseType { [JsonConverter(typeof(JsonStringEnumConverter))] public abstract BlockType Type { get; } } public class Type1 : BaseType { public override BlockType Type => BlockType.T1; required public int Value1 { get; set; } } public class Type2 : BaseType { public override BlockType Type => BlockType.T2; required public int Value2 { get; set; } } 

In this case your JSON string will be the same as you provided.

Sign up to request clarification or add additional context in comments.

1 Comment

I prefer Solution 2 because we don't need to duplicate the type field in the json
1

The How to serialize properties of derived classes with System.Text.Json has the following recommendation:

Avoid using a JsonPolymorphicAttribute.TypeDiscriminatorPropertyName that conflicts with a property in your type hierarchy.

So probably the easiest approach would be to remove the property from the hierarchy and use lowercase type as discriminator:

[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")] [JsonDerivedType(typeof(Type1), nameof(BlockType.T1))] [JsonDerivedType(typeof(Type2), nameof(BlockType.T2))] public class BaseType { } 

If you want to keep the property then you can use the pre-NET 7 option with custom serializer or make the property ignored:

[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")] [JsonDerivedType(typeof(Type1), nameof(BlockType.T1))] [JsonDerivedType(typeof(Type2), nameof(BlockType.T2))] public abstract class BaseType { [System.Text.Json.Serialization.JsonIgnore] public abstract BlockType Type { get; } } public class Type1 : BaseType { [System.Text.Json.Serialization.JsonIgnore] public override BlockType Type => BlockType.T1; public required int Value1 { get; set; } } public class Type2 : BaseType { [System.Text.Json.Serialization.JsonIgnore] public override BlockType Type => BlockType.T2; public required int Value2 { get; set; } } 

2 Comments

This will bring an issue when serializing derived type: f.e. JsonSerializer.Serialize(new Type2()), no discriminator will be written. We can force it use its base type JsonSerializer.Serialize<BaseType>(new Type2()), but it's hard to do it in aspnetcore.
@iroel depending on the case you can either provide a generic type parameter in corresponding place or write custom serializer which will serialize type as a base one.
0

I have personally gave up on using [JsonPolymorphic] and instead wrote a converter. A converter for the above class would look something like this

public class BaseTypeConverter : JsonConverter<BaseType> { public override BaseType? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { using var doc = JsonDocument.ParseValue(ref reader); var typestr = doc.RootElement.GetProperty("type").GetString()?.ToUpper(); if (Enum.TryParse(typestr, out BlockType type)) { if (type == BlockType.T1) { return JsonSerializer.Deserialize<Type1>(doc.RootElement.GetRawText(), options); } else { return JsonSerializer.Deserialize<Type2>(doc.RootElement.GetRawText(), options); } } } public override void Write(Utf8JsonWriter writer, BaseType value, JsonSerializerOptions options) { // I'm only interested in deserializing throw new NotImplementedException(); } } 

1 Comment

please review my answer to your question. You can implement deserialization without custom converter.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.