. net Core序列化继承的类属性——在保留引用时不仅仅是基属性



使用System.Text.Json.JsonSerializer序列化时出现问题。

在这个例子中,我们有三个类:Store,EmployeeManager。需要注意的是,Manager继承自Employee。

public class Employee
{
public string Name { get; set; }
public int Age { get; set; }
}
public class Manager : Employee
{
public int AllowedPersonalDays { get; set; }
}
public class Store
{
public Employee EmployeeOfTheMonth { get; set; }
public Manager Manager { get; set; }
public string Name { get; set; }
}

Store类中,我们有一个称为EmployeeOfTheMonth的属性。作为一个例子,假设这个属性引用了与Manager属性相同的对象。因为EmployeeOfTheMonth首先被序列化,所以它只会序列化Employee属性。当序列化Manager属性时——因为它是第二个相同的对象——它将添加对EmployeeOfTheMonth的引用。当我们这样做的时候,我们失去了附加在Manager上的额外属性,即AllowedPersonalDays。此外,正如您所看到的,它不会反序列化,因为—虽然Manager是Employee—Employee不是Manager。

下面是我们的简短示例:

Manager mgr = new Manager()
{
Age = 42,
AllowedPersonalDays = 14,
Name = "Jane Doe",
};
Store store = new Store()
{
EmployeeOfTheMonth = mgr,
Manager = mgr,
Name = "ValuMart"
};
System.Text.Json.JsonSerializerOptions options = new System.Text.Json.JsonSerializerOptions();
options.ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.Preserve;
string serialized = System.Text.Json.JsonSerializer.Serialize<Store>(store, options);
var deserialized = System.Text.Json.JsonSerializer.Deserialize<Store>(serialized, options); // <-- Will through an exception per reasons stated above

如果我们看一下变量serialized,这是内容:

{
"$id":"1",
"EmployeeOfTheMonth": {
"$id":"2",
"Name":"Jane Doe",
"Age":42
},
"Manager": {
"$ref":"2"
},
"Name":"ValuMart"
}

使用System.Text.Json。JsonSerializer,我们怎么能得到EmployeeOfTheMonth正确序列化为Manager?也就是说,我们需要序列化如下所示:

{
"$id":"1",
"EmployeeOfTheMonth": {
"$id":"2",
"Name":"Jane Doe",
"Age":42,
"AllowedPersonalDays":14         <-- We need to retain this property even if the EmployeeOfTheMonth is a Manager
},
"Manager": {
"$ref":"2"
},
"Name":"ValuMart"
}

我知道我可以调整Store类属性的顺序,但这不是一个选项,也是一个非常糟糕的选择。谢谢大家。

关于编写自定义转换器的文档有一个非常相似的例子(区分属性声明类型的两个子类),可以按照如下方式改编:

public class EmployeeConverter : JsonConverter<Employee>
{
enum TypeDiscriminator
{
Employee = 1,
Manager = 2
}
private static string s_typeDiscriminatorLabel = "$TypeDiscriminator";
public override bool CanConvert(Type typeToConvert) =>
typeof(Employee).IsAssignableFrom(typeToConvert);
public override Employee Read(
ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.StartObject)
{
throw new JsonException();
}
reader.Read();
if (reader.TokenType != JsonTokenType.PropertyName)
{
throw new JsonException();
}
string propertyName = reader.GetString();
if (propertyName != s_typeDiscriminatorLabel)
{
throw new JsonException();
}
reader.Read();
if (reader.TokenType != JsonTokenType.Number)
{
throw new JsonException();
}
// Instantiate type based on type discriminator value
TypeDiscriminator typeDiscriminator = (TypeDiscriminator)reader.GetInt32();
Employee employee = typeDiscriminator switch
{
TypeDiscriminator.Employee => new Employee(),
TypeDiscriminator.Manager => new Manager(),
_ => throw new JsonException()
};
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject)
{
return employee;
}
if (reader.TokenType == JsonTokenType.PropertyName)
{
propertyName = reader.GetString();
reader.Read();
switch (propertyName)
{
case "Name":
string name = reader.GetString();
employee.Name = name;
break;
case "Age":
int age = reader.GetInt32();
employee.Age = age;
break;
case "AllowedPersonalDays":
int allowedPersonalDays = reader.GetInt32();
if(employee is Manager manager)
{
manager.AllowedPersonalDays = allowedPersonalDays;
}
else
{
throw new JsonException();
}
break;
}
}
}
throw new JsonException();
}
public override void Write(
Utf8JsonWriter writer, Employee person, JsonSerializerOptions options)
{
writer.WriteStartObject();
// Write type indicator based on whether the runtime type is Manager
writer.WriteNumber(s_typeDiscriminatorLabel, (int)(person is Manager ? TypeDiscriminator.Manager : TypeDiscriminator.Employee));
writer.WriteString("Name", person.Name);
writer.WriteNumber("Age", person.Age);
// Write Manager-ony property only if runtime type is Manager
if(person is Manager manager)
{
writer.WriteNumber("AllowedPersonalDays", manager.AllowedPersonalDays);
}
writer.WriteEndObject();
}
}

添加自定义转换器的实例,它应该正确地反序列化:

options.Converters.Add(new EmployeeConverter());
string serialized = JsonSerializer.Serialize<Store>(store, options);
var deserialized = JsonSerializer.Deserialize<Store>(serialized, options);
string reserialized = JsonSerializer.Serialize<Store>(deserialized, options);
System.Diagnostics.Debug.Assert(serialized == reserialized, "Manager property should be retained");

最新更新