c# - System.Text.Json 序列化不适用于抽象成员

标签 c# json.net polymorphism system.text.json

我有以下接口(interface)及其实现(带有 Newtonsoft.JsonSystem.Text.Json 的 JSON 序列化器):

public interface IAmount {
    decimal Value { get; }
}

[Newtonsoft.Json.JsonConverter(typeof(NewtonsoftJsonConverter))]
[System.Text.Json.Serialization.JsonConverter(typeof(SystemTextJsonConverter))]
public class Amount : IAmount {
    public Amount(decimal value) {
        Value = value;
    }

    public decimal Value { get; }
}

public class NewtonsoftJsonConverter : Newtonsoft.Json.JsonConverter {
    public override bool CanConvert(Type objectType) => objectType.IsAssignableTo(typeof(IAmount));

    public override object? ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object? existingValue, Newtonsoft.Json.JsonSerializer serializer) {
        throw new NotImplementedException();
    }

    public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object? value, Newtonsoft.Json.JsonSerializer serializer) {
        writer.WriteRawValue(((IAmount?)value)?.Value.ToString());
    }
}

public class SystemTextJsonConverter : System.Text.Json.Serialization.JsonConverter<object> {
    public override bool CanConvert(Type typeToConvert) => typeToConvert.IsAssignableTo(typeof(IAmount));

    public override object Read(ref System.Text.Json.Utf8JsonReader reader, Type typeToConvert, System.Text.Json.JsonSerializerOptions options) {
        throw new NotImplementedException();
    }

    public override void Write(System.Text.Json.Utf8JsonWriter writer, object value, System.Text.Json.JsonSerializerOptions options) {
        writer.WriteRawValue(((IAmount)value).Value.ToString());
    }
}

如果我的对象的类型为Amount,则效果很好。例如(在每行旁边的注释中输出):

var foo = new Amount(10);

Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(foo)); // 10
Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(foo)); // 10

但是,如果对象的类型为 IAmount,则它适用于 Newtonsoft.Json,但不适用于 System.Text.Json。例如:

IAmount foo = new Amount(10);

Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(foo)); // 10
Console.WriteLine(System.Text.Json.JsonSerializer.Serialize(foo)); // {"Value":10}

如您所见,使用 System.Text.Json 时的输出有所不同。我尝试在 CanCovert 方法上设置断点,但它从未被调用。

我可以通过在界面上添加 [System.Text.Json.Serialization.JsonConverter(typeof(SystemTextJsonConverter))] 属性来解决此问题,但理想情况下我不希望这样做。有谁知道无需修改界面即可解决此问题的替代解决方案?

请注意,切换到 Newtonsoft 不是一个选择。

最佳答案

我相信这是设计好的。 System.Text.Json 故意在序列化期间不支持多态性,除非要序列化的对象显式声明为 object 时。来自 docs :

Serialize properties of derived classes

Serialization of a polymorphic type hierarchy is not supported. For example, if a property is defined as an interface or an abstract class, only the properties defined on the interface or abstract class are serialized, even if the runtime type has additional properties. The exceptions to this behavior are explained in this section....

To serialize the properties of [a] derived type, use one of the following approaches:

  1. Call an overload of Serialize that lets you specify the type at runtime...

  2. Declare the object to be serialized as object.

虽然文档仅声明派生类的属性未序列化,但我相信,由于 System.Text.Json 内部是基于契约的序列化程序,因此在序列化派生类型时,它使用声明类型的整个契约。因此,元数据(包括 JsonConverterAttribute 和已应用的任何其他 JSON attributes)以及属性是通过反射(reflect)声明的类型(此处为 IAmount)而不是实际的类型来获取的。输入(此处为金额)。

那么,您有哪些选择来解决此限制?

首先,如果 IAmount 仅实现为 Amount,您可以引入一个 JsonConverter,它始终将一种类型序列化为其他兼容类型:

public class AbstractToConcreteConverter<TAbstract, TConcrete> : JsonConverter<TAbstract> where TConcrete : TAbstract
{
    static AbstractToConcreteConverter()
    {
        if (typeof(TAbstract) == typeof(TConcrete))
            throw new ArgumentException(string.Format("Identical type {0} used for both TAbstract and TConcrete", typeof(TConcrete)));
    }
    
    public override TAbstract? Read(ref System.Text.Json.Utf8JsonReader reader, Type typeToConvert, System.Text.Json.JsonSerializerOptions options) =>
        JsonSerializer.Deserialize<TConcrete>(ref reader, options);

    public override void Write(System.Text.Json.Utf8JsonWriter writer, TAbstract value, System.Text.Json.JsonSerializerOptions options) =>
        JsonSerializer.Serialize(writer, (TConcrete)value!, options);
}

并将其应用于IAmount:

[JsonConverter(typeof(AbstractToConcreteConverter<IAmount, Amount>))]
public interface IAmount {
    decimal Value { get; }
}

演示 fiddle #1 here .

其次,如果您根本不关心反序列化,并且希望将所有声明为接口(interface)的值序列化为其具体类型,则可以引入一个转换器工厂来执行此操作:

public class ConcreteInterfaceSerializer : JsonConverterFactory
{
    public override bool CanConvert(Type typeToConvert) => typeToConvert.IsInterface;
    
    class ConcreteInterfaceSerializerOfType<TInterface> : JsonConverter<TInterface> 
    {
        static ConcreteInterfaceSerializerOfType()
        {
            if (!typeof(TInterface).IsAbstract && !typeof(TInterface).IsInterface)
                throw new NotImplementedException(string.Format("Concrete class {0} is not supported", typeof(TInterface)));
        }   
    
        public override TInterface? Read(ref System.Text.Json.Utf8JsonReader reader, Type typeToConvert, System.Text.Json.JsonSerializerOptions options) =>
            throw new NotImplementedException();

        public override void Write(System.Text.Json.Utf8JsonWriter writer, TInterface value, System.Text.Json.JsonSerializerOptions options) =>
            JsonSerializer.Serialize<object>(writer, value!, options);
    }
    
    public override JsonConverter CreateConverter(Type type, JsonSerializerOptions options) => 
        (JsonConverter)Activator.CreateInstance(
            typeof(ConcreteInterfaceSerializerOfType<>).MakeGenericType(new Type[] { type }),
            BindingFlags.Instance | BindingFlags.Public,
            binder: null,
            args: Array.Empty<object>(),
            culture: null).ThrowOnNull();
}

public static class ObjectExtensions
{
    public static T ThrowOnNull<T>(this T? value) where T : class => value ?? throw new ArgumentNullException();
}

或者直接将其应用到IAmount:

[JsonConverter(typeof(ConcreteInterfaceSerializer))]
public interface IAmount {
    decimal Value { get; }
}

或者将其添加到选项中:

var options = new JsonSerializerOptions
{
    Converters = { new ConcreteInterfaceSerializer() },
};
var systemJson = System.Text.Json.JsonSerializer.Serialize<IAmount>(foo, options);

演示 fiddle #2 here .

关于c# - System.Text.Json 序列化不适用于抽象成员,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72774698/

相关文章:

c# - 如何从具有组件输出的设备获取高清信号并将其显示在 C# 应用程序的窗口中?

c# - 更改 System.Media.SoundPlayer 中的音量

c# - 根据类型反序列化json字符串

c# - JSON 反序列化为构造的 protected setter 数组

SQL、MVC、 Entity Framework

ruby-on-rails - 使用 Polymorphic Paperclip 生成唯一的文件路径

javascript - 识别 Typescript 中接口(interface)和类之间的多态性

c# - 使用 Azure Service Fabric 与自定义 Azure 云的优缺点?

C# tcp server-client,无法发送消息

javascript - 如何将 JSON.NET 日期时间格式转换为 JavaScript 日期和时间