[英]System.Text.Json serialization doesn't work for abstract members
我有以下接口及其實現(帶有用於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
。 從文檔:
序列化派生類的屬性
例如,如果一個屬性被定義為接口或抽象類,那么即使運行時類型具有附加屬性,也只會序列化接口或抽象類上定義的屬性。 此行為的例外情況將在本節中解釋......
要序列化 [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在這里。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.