使用自定义转换器进行的多态 Json 序列化/反序列化在 .NET 8 中被破坏

问题描述 投票:0回答:1

我有一些使用多态性的类,我需要通过 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);
            }
        }
    }
}
c# json polymorphism system.text.json .net-8.0
1个回答
1
投票

自 .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""}";
© www.soinside.com 2019 - 2024. All rights reserved.