I have some classes using polymorphism and I need to send them via an OData Controller to the client. On the client I have added Converters to JsonSerializerOptions in order to determine the correct type to serialize/deserialize these Objects.
On server side the classes are decorated with [JsonDerivedType...] OData stores the Type in @odata.type Property.
The Converter used is a TypeDiscriminatingConverter of Generic type, deriving from JsonConvert, basically reading @odata.type Property and serializing/deserializing into the correct type. It's very similar to the first Code Snippet in https://bengribaudo.com/blog/2022/02/22/6569/recursive-polymorphic-deserialization-with-system-text-json.
I am currently doing the update to .NET 8 and on the client side I am now getting the Error
System.NotSupportedException: The converter for derived type 'xy' does not support metadata writes or reads.
at System.Text.Json.ThrowHelper.ThrowNotSupportedException_BaseConverterDoesNotSupportMetadata(Type derivedType)
at System.Text.Json.Serialization.Metadata.PolymorphicTypeResolver..ctor(JsonSerializerOptions options, JsonPolymorphismOptions polymorphismOptions, Type baseType, Boolean converterCanHaveMetadata)
at System.Text.Json.Serialization.Metadata.JsonTypeInfo.Configure()
at System.Text.Json.Serialization.Metadata.JsonTypeInfo.<EnsureConfigured>g__ConfigureSynchronized|172_0()
at System.Text.Json.Serialization.Metadata.JsonTypeInfo.EnsureConfigured()
I did research on the Error and Polymorphism in .NET8 using System.Text.Json, but I could not find good ressources in the Web apart from Microsofts Documentation. Finally I dived into System.Text.Json 8.0.1 sources to understand better where the Error arises from. What I found out is, that in PolymorphicTypeResolver it is a must that the Converter can have MetaData if TypeDiscriminators are used. Well, as I see it, that CanHaveMetadata is an internal sealed Property thats not thought to be used by a custom converter.
What can I do? What am I missing?
Currently doing a minimalistic example... will take a little Hoped I am missing some general point which wouldn't need an example.
Here is the minimalistic console app to reproduce:
// See https://aka.ms/new-console-template for more information
using System.Text.Json;
using System.Text.Json.Serialization;
string derivedjson = @"{""$type"":0,""@odata.type"":""derived"",""BaseName"":""base1"",""Name"":""derived1""}";
JsonSerializerOptions options = new JsonSerializerOptions()
{
Converters =
{
new TypeDiscriminatingConverter<Base>((ref Utf8JsonReader reader) =>
{
using var doc = JsonDocument.ParseValue(ref reader);
var typeDiscriminator = doc.RootElement.GetProperty("@odata.type").GetString();
if (typeDiscriminator!.Contains("derived"))
{
return typeof(Derived);
}
throw new JsonException();
})
},
WriteIndented = false,
ReferenceHandler = ReferenceHandler.IgnoreCycles,
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};
var deserialized = JsonSerializer.Deserialize<Base>(derivedjson, options);
public class BaseBase
{
public string BaseBaseName { get; set; } = "basebase";
}
public enum PublicEnum
{
PublicEnumValue = 0
}
[JsonPolymorphic(UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FailSerialization)]
[JsonDerivedType(typeof(Derived), typeDiscriminator: nameof(PublicEnum.PublicEnumValue))]
public class Base:BaseBase
{
public string BaseName { get; set; } = "base";
}
public class Derived:Base
{
public string Name { get; set; } = "derived";
}
public class TypeDiscriminatingConverter<T> : JsonConverter<T>
{
public delegate Type TypeDiscriminatorConverter(ref Utf8JsonReader reader);
private readonly TypeDiscriminatorConverter Converter;
private Dictionary<Type, string> _typeToPropertyString = new Dictionary<Type, string>
{
{typeof(Derived),$"\"$type\":\"0\","},
};
public TypeDiscriminatingConverter(TypeDiscriminatorConverter converter) =>
(Converter) = (converter);
public override bool CanConvert(Type typeToConvert) =>
typeToConvert == typeof(T);
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var typeCalculatorReader = reader;
var actualType = Converter(ref typeCalculatorReader);
return (T?)JsonSerializer.Deserialize(ref reader, actualType, options);
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
if (value is null)
{
writer.WriteNullValue();
}
else
{
var type = value.GetType();
if (_typeToPropertyString.TryGetValue(type, out string? typeString))
{
string temp = JsonSerializer.Serialize(value, type, options);
temp = temp.Insert(1, typeString);
writer.WriteRawValue(temp);
}
else
{
JsonSerializer.Serialize(writer, value, type, options);
}
}
}
}
Since .NET 7 there is no need for extra converter - see the How to serialize properties of derived classes with System.Text.Json
doc. Remove it and fix the JsonDerivedTypeAttribute
:
[JsonPolymorphic(UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FailSerialization)]
[JsonDerivedType(typeof(Derived), (int)PublicEnum.PublicEnumValue)]
public class Base : BaseBase
{
public string BaseName { get; set; } = "base";
}
JsonSerializerOptions options = new JsonSerializerOptions()
{
WriteIndented = false,
ReferenceHandler = ReferenceHandler.IgnoreCycles,
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};
// ...
Note that if you can remove the $type
property from JSON you can just use @odata.type
property as discriminator:
[JsonPolymorphic(UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FailSerialization, TypeDiscriminatorPropertyName = "@odata.type" )]
[JsonDerivedType(typeof(Derived), "derived")]
public class Base : BaseBase
{
public string BaseName { get; set; } = "base";
}
string derivedjson = @"{""@odata.type"":""derived"",""BaseName"":""base1"",""Name"":""derived1""}";