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



我有以下接口和它的实现(使用JSON序列化器为Newtonsoft.JsonSystem.Text.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不是一个选项。

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

派生类的序列化属性

不支持多态类型层次结构的序列化。 为例,如果一个属性被定义为一个接口或抽象类,只有属性定义在接口或抽象类序列化,即使运行时类型有额外的属性。此行为的例外情况将在本节....中解释

要序列化[a]派生类型的属性,请使用以下方法之一:

  1. 调用序列化的重载,允许您在运行时指定类型…

  2. 声明要序列化的对象为object

虽然文档只声明派生类的属性不序列化,但我认为,由于System.Text.Json在内部是一个基于契约的序列化器,因此在序列化派生类型时,它使用声明类型的整个契约。因此,元数据(包括JsonConverterAttribute和任何其他已应用的JSON属性)以及属性都是通过反映声明的类型(这里是IAmount)而不是实际类型(这里是Amount)来获取的。

那么,你有什么办法来解决这个限制呢?

首先,如果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; }
}

在这里演示小提琴#1。

其次,如果你根本不关心反序列化,而是希望所有声明为接口的值都被序列化为它们的具体类型,你可以引入一个转换器工厂来做这件事:
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; }
}

或者添加到options:

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

在这里演示小提琴#2。