我有以下接口及其实现(带有
Newtonsoft.Json
和 System.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
或(从 .NET 7 开始)启用其 选择加入对多态性的支持。来自文档:
序列化派生类的属性
在 .NET 7 之前的版本中,不支持多态类型层次结构的序列化。例如,如果属性的类型是接口或抽象类,则仅支持在接口或抽象上定义的属性即使运行时类型具有其他属性,类也会被序列化。本节解释了此行为的例外情况。有关 .NET 7 中支持的信息,请参阅.NET 7 中的多态序列化。System.Text.Json
要序列化[a]派生类型的属性,请使用以下方法之一:
调用 Serialize 的重载,让您可以在运行时指定类型...
声明要序列化的对象为
。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; }
}
或者在选项中添加:
var options = new JsonSerializerOptions
{
Converters = { new ConcreteInterfaceSerializer() },
};
var systemJson = System.Text.Json.JsonSerializer.Serialize<IAmount>(foo, options);
演示小提琴 #2 这里。