我有以下接口(interface)及其实现(带有 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
时。来自 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:
Call an overload of Serialize that lets you specify the type at runtime...
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/