我目前在 Blazor 应用程序中遇到了同样的问题,所以我无法Newtonsoft.Json
轻松切换到。我找到了两种方法。一种是现实中的hack。您可以创建自定义转换器,Newtonsoft.Json
在读/写方法中使用,而不是System.Text.Json
. 但这不是我想要的。所以我做了一些自定义界面转换器。我有一些可行的解决方案,尚未经过广泛测试,但它可以满足我的需要。
情况
我有一个List<TInterface>
对象实现TInterface
. 但是有很多不同的实现。我需要在服务器上序列化数据,并在客户端 WASM 应用程序上反序列化所有数据。对于 JavaScript 反序列化,后面提到的自定义 Write 方法的实现就足够了。对于 C# 中的反序列化,我需要知道为列表中的每个项目序列化的对象的确切类型。
首先,我需要JsonConverterAttribute
在界面上。所以我关注了这篇文章:https ://khalidabuhakmeh.com/serialize-interface-instances-system-text-json 。有一些实现Writer
,它将处理接口类型。但没有Read
实施。所以我必须自己做。
如何
- 修改
Write
方法以将对象类型作为第一个属性写入 JSON 对象。使用 JsonDocument 从原始对象中获取所有属性。
- 读取 JSON 时,使用克隆阅读器(如Microsoft 文档中针对自定义 json 转换器的建议)查找
$type
以类型信息命名的第一个属性。比创建该类型的实例并使用类型来反序列化来自原始阅读器的数据。
代码
接口和类:
[JsonInterfaceConverter(typeof(InterfaceConverter<ITest>))]
public interface ITest
{
int Id { get; set; }
string Name { get; set; }
}
public class ImageTest : ITest
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Image { get; set; } = string.Empty;
}
public class TextTest : ITest
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Text { get; set; } = string.Empty;
public bool IsEnabled { get; set; }
}
接口转换器属性:
// Source: https://khalidabuhakmeh.com/serialize-interface-instances-system-text-json
[AttributeUsage(AttributeTargets.Interface, AllowMultiple = false)]
public class JsonInterfaceConverterAttribute : JsonConverterAttribute
{
public JsonInterfaceConverterAttribute(Type converterType)
: base(converterType)
{
}
}
转换器:
public class InterfaceConverter<T> : JsonConverter<T>
where T : class
{
public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
Utf8JsonReader readerClone = reader;
if (readerClone.TokenType != JsonTokenType.StartObject)
{
throw new JsonException();
}
readerClone.Read();
if (readerClone.TokenType != JsonTokenType.PropertyName)
{
throw new JsonException();
}
string propertyName = readerClone.GetString();
if (propertyName != "$type")
{
throw new JsonException();
}
readerClone.Read();
if (readerClone.TokenType != JsonTokenType.String)
{
throw new JsonException();
}
string typeValue = readerClone.GetString();
var instance = Activator.CreateInstance(Assembly.GetExecutingAssembly().FullName, typeValue).Unwrap();
var entityType = instance.GetType();
var deserialized = JsonSerializer.Deserialize(ref reader, entityType, options);
return (T)deserialized;
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
switch (value)
{
case null:
JsonSerializer.Serialize(writer, (T)null, options);
break;
default:
{
var type = value.GetType();
using var jsonDocument = JsonDocument.Parse(JsonSerializer.Serialize(value, type, options));
writer.WriteStartObject();
writer.WriteString("$type", type.FullName);
foreach (var element in jsonDocument.RootElement.EnumerateObject())
{
element.WriteTo(writer);
}
writer.WriteEndObject();
break;
}
}
}
}
用法:
var list = new List<ITest>
{
new ImageTest { Id = 1, Name = "Image test", Image = "some.url.here" },
new TextTest { Id = 2, Name = "Text test", Text = "kasdglaskhdgl aksjdgl asd gasdg", IsEnabled = true },
new TextTest { Id = 3, Name = "Text test 2", Text = "asd gasdg", IsEnabled = false },
new ImageTest { Id = 4, Name = "Second image", Image = "diff.url.here" }
};
var json = JsonSerializer.Serialize(list);
var data = JsonSerializer.Deserialize<List<ITest>>(json);
// JSON data
// [
// {
// "$type":"ConsoleApp1.ImageTest",
// "Id":1,
// "Name":"Image test",
// "Image":"some.url.here"
// },
// {
// "$type":"ConsoleApp1.TextTest",
// "Id":2,
// "Name":"Text test",
// "Text":"kasdglaskhdgl aksjdgl asd gasdg",
// "IsEnabled":true
// },
// {
// "$type":"ConsoleApp1.TextTest",
// "Id":3,
// "Name":"Text test 2",
// "Text":"asd gasdg",
// "IsEnabled":false
// },
// {
// "$type":"ConsoleApp1.ImageTest",
// "Id":4,
// "Name":"Second image",
// "Image":"diff.url.here"
// }
// ]
编辑:我用这个逻辑制作了一个 NuGet 包。你可以在这里下载:InterfaceConverter.SystemTextJson