我有一些使用多态性的类,我需要通过 OData 控制器将它们发送到客户端。在客户端上,我已将转换器添加到 JsonSerializerOptions 以确定序列化/反序列化这些对象的正确类型。
在服务器端,类用 [JsonDerivedType...] 装饰 OData 将类型存储在 @odata.type 属性中。
使用的Converter是Generic类型的TypeDiscriminatingConverter,源自JsonConvert,基本上读取@odata.type属性并序列化/反序列化为正确的类型。它与 https://bengribaudo.com/blog/2022/02/22/6569/recursive-polymorphic-deserialization-with-system-text-json中的第一个代码片段非常相似。
我目前正在更新 .NET 8,在客户端我现在收到错误
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()
我使用 System.Text.Json 研究了 .NET8 中的错误和多态性,但除了 Microsoft 文档之外,我在 Web 上找不到好的资源。最后,我深入研究了 System.Text.Json 8.0.1 源代码,以更好地了解错误的根源。我发现,在 PolymorphicTypeResolver 中,如果使用 TypeDiscriminators,则转换器必须具有元数据。嗯,正如我所见,CanHaveMetadata 是一个内部密封属性,不认为可以由自定义转换器使用。
我能做什么?我错过了什么?
目前正在做一个简约的例子......需要一点时间 希望我遗漏了一些不需要示例的一般要点。
这是要重现的简约控制台应用程序:
// 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);
}
}
}
}
自 .NET 7 起,不再需要额外的转换器 - 请参阅 如何使用
System.Text.Json
文档序列化派生类的属性。拆下它并修复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,
};
// ...
请注意,如果您可以从 JSON 中删除
$type
属性,则可以仅使用 @odata.type
属性作为鉴别器:
[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""}";