You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET 8下自定义转换器的多态JSON序列化/反序列化异常问题

.NET 8 中自定义多态JSON转换器兼容OData类型信息的问题解决

我有一组使用多态特性的类,通过OData控制器推送给客户端。客户端通过在JsonSerializerOptions中添加自定义转换器来处理对象的序列化/反序列化,以此确定正确的类型。服务端的类已经标记了[JsonDerivedType...]特性,OData会把类型信息存在@odata.type属性里。

我用的泛型TypeDiscriminatingConverter继承自JsonConverter,通过读取@odata.type属性来完成正确类型的序列化/反序列化,但升级到.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 8.0.1的源码,发现PolymorphicTypeResolver要求使用类型鉴别器时,转换器必须支持元数据,但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 8中,当你为基类添加[JsonPolymorphic]和[JsonDerivedType]特性后,System.Text.Json会自动启用内置多态处理逻辑,这和自定义转换器的逻辑冲突,导致抛出元数据支持异常。由于自定义转换器已经完全接管类型鉴别,不需要依赖内置特性,直接删除这些标记即可。

  • 避免转换器递归调用:在Read和Write方法中,反序列化/序列化实际类型时,要创建排除当前自定义转换器的JsonSerializerOptions副本,否则会陷入无限递归。修改后的核心逻辑如下:

public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
    var typeCalculatorReader = reader;
    var actualType = Converter(ref typeCalculatorReader);

    // 创建排除当前转换器的选项副本
    var newOptions = new JsonSerializerOptions(options);
    newOptions.Converters.RemoveAll(c => c.GetType() == typeof(TypeDiscriminatingConverter<T>));

    return (T?)JsonSerializer.Deserialize(ref reader, actualType, newOptions);
}

public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
    if (value is null)
    {
        writer.WriteNullValue();
        return;
    }

    var type = value.GetType();
    var newOptions = new JsonSerializerOptions(options);
    newOptions.Converters.RemoveAll(c => c.GetType() == typeof(TypeDiscriminatingConverter<T>));

    if (_typeToPropertyString.TryGetValue(type, out string? typeString))
    {
        string temp = JsonSerializer.Serialize(value, type, newOptions);
        temp = temp.Insert(1, typeString);
        writer.WriteRawValue(temp);
    }
    else
    {
        JsonSerializer.Serialize(writer, value, type, newOptions);
    }
}
  • 统一类型鉴别符:确保序列化和反序列化使用一致的类型属性,比如依赖OData的@odata.type,就把转换器中写入的$type改为@odata.type,避免逻辑混淆:
private Dictionary<Type, string> _typeToPropertyString = new Dictionary<Type, string>
{
    {typeof(Derived), "\"@odata.type\":\"derived\","},
};

内容的提问来源于stack exchange,提问作者phil ius

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 11:12:34