我目前正在将 vom Newtonsoft 迁移到 System.Text.Json。 Newtonsoft 能够自动反序列化具有一个或多个接口(interface)属性的对象。使用 System.Text.Json,当我尝试完成相同的任务时,我会收到相应类的以下错误消息:
反序列化构造函数中的每个参数必须绑定(bind)到反序列化时的对象属性或字段。每个参数名称必须与对象上的属性或字段匹配。
我可以通过编写自定义转换器来避免这个问题,但这会导致具有多个嵌套层的对象产生大量开销,其中每个 Interface 属性又可以拥有自己的多个 Interface 属性(因此需要多个自定义转换器) )。有没有更简单的解决方案来解决这个问题?
我创建一个示例来说明问题:
[Fact]
public void JsonTest()
{
var child = new Child(new Name("Peter"), 10);
var parent = new Parent(child);
var str = JsonSerializer.Serialize(parent);
var jsonObj = JsonSerializer.Deserialize<Parent>(str);
Console.WriteLine(jsonObj!.Child.Name);
}
}
[JsonConverter(typeof(ParentConverter))]
public class Parent
{
public Parent(Child child)
{
Child = child;
}
public IChild Child { get; set;}
}
[JsonConverter(typeof(ChildConverter))]
public class Child : IChild
{
[JsonConstructor]
public Child(Name name, int age)
{
Name = name;
Age = age;
}
public IName Name { get; set; }
public int Age { get; set; }
}
public class Name : IName
{
public string NameValue { get; set; }
public Name(string nameValue)
{
NameValue = nameValue;
}
}
public interface IChild
{
IName Name { get; }
int Age { get; }
}
public interface IName
{
string NameValue { get; }
}
public class ParentConverter : JsonConverter<Parent>
{
public override Parent Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
using (JsonDocument document = JsonDocument.ParseValue(ref reader))
{
JsonElement root = document.RootElement;
if (root.TryGetProperty("Child", out JsonElement childElement))
{
Child? child = JsonSerializer.Deserialize<Child>(childElement.GetRawText(), options);
return new Parent(child!);
}
}
throw new JsonException("Invalid JSON data");
}
public override void Write(Utf8JsonWriter writer, Parent value, JsonSerializerOptions options)
{
writer.WriteStartObject();
writer.WritePropertyName("Child");
JsonSerializer.Serialize(writer, value.Child, options);
writer.WriteEndObject();
}
}
public class ChildConverter : JsonConverter<Child>
{
public override Child Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
using (JsonDocument document = JsonDocument.ParseValue(ref reader))
{
var name = new Name("Alex");
var age = 20;
JsonElement root = document.RootElement;
if (root.TryGetProperty("Name", out JsonElement nameElement))
{
name = JsonSerializer.Deserialize<Name>(nameElement.GetRawText(), options);
}
if (root.TryGetProperty("Age", out JsonElement ageElement))
{
age = JsonSerializer.Deserialize<int>(ageElement.GetRawText(), options);
}
return new Child(name!, age);
}
throw new JsonException("Invalid JSON data");
}
public override void Write(Utf8JsonWriter writer, Child value, JsonSerializerOptions options)
{
writer.WriteStartObject();
writer.WritePropertyName("Name");
JsonSerializer.Serialize(writer, value.Name, options);
writer.WritePropertyName("Age");
JsonSerializer.Serialize(writer, value.Age, options);
writer.WriteEndObject();
}
}
通过这些自定义转换器,我得到了预期的行为。但是有没有办法避免为每个这样的类编写自定义转换器?
最佳答案
这称为多态序列化。该库需要某种方式来注释属性实际上是什么 IChild
的具体实现。我认为 Newtonsoft 添加了一个具有完整类型名称的属性,虽然这很容易使用,但如果您想重命名您的类,它有一些潜在的缺点。
System.Text.Json 使用属性来注释派生类型,并要求您提供标识符。了解更多关于 Polymorphic type discriminators .
[JsonDerivedType(typeof(Child1), typeDiscriminator: "Child1")]
[JsonDerivedType(typeof(Child2), typeDiscriminator: "Child2")]
public interface IChild
{
int Age { get; }
}
public class Child1 : IChild
{
public string PreferedToy { get; set; }
public int Age { get; set; }
}
public class Child2 : IChild
{
public string Name { get; set; }
public int Age { get; set; }
}
如果每个接口(interface)只有一个实现,我会考虑要么放弃这些接口(interface),要么将对象转换为尽可能简单地序列化的数据传输对象 (DTO)。使用 DTO 有助于将序列化问题与类中的任何逻辑分开。
关于c# - System.Text.Json:如何使用接口(interface)属性反序列化类(.NET 6),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/76529636/