使用 System.Text.Json 反序列化复杂的多态类型
Deserialize complex polymorphic types with System.Text.Json
文档中的 dotnet 示例:
显示手动解析每个 属性 多态类型。然而:
- 我的多态对象是复杂的深层层次结构,我无法手动编写每个字段的代码,因此我需要调用
JsonSerializer
.
- 类型的线索在同级字段中指定。由于无法保证 json 元素顺序,因此
Utf8JsonReader
在遇到多态类型之前可能没有读取类型信息。
例如
[JsonConverter(typeof(MessageConverter))]
public class Message
{
public string Type { get; set; } // indicates what implementation IBody is
public IBody Body { get; set; }
}
public interface IBody
{
}
public class BodyA : IBody
{
// a big object hierarchy but just showing one property for simplicity
public string A { get; set; }
}
public class BodyB : IBody
{
// a big object hierarchy but just showing one property for simplicity
public string B { get; set; }
}
public class MessageConverter : JsonConverter<Message>
{
public override bool CanConvert(Type objectType) =>
objectType == typeof(Message);
public override Message Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var message = new Message();
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject)
{
break;
}
if (reader.TokenType == JsonTokenType.PropertyName)
{
var propertyName = reader.GetString();
reader.Read();
switch (propertyName)
{
case "Type":
message.Type = reader.GetString();
break;
case "Body":
// Body might be read before "Message.Type" so can't parse it yet
message.Body = /* help - what am I? */;
break;
}
}
}
return message;
}
public override void Write(Utf8JsonWriter writer, Message value, JsonSerializerOptions options)
throw new NotImplementedException();
}
正在查看Utf8JsonReader
:
- 有没有办法查看未来的元素或将解析器位置后移?
- 是否有有效的方法来缓存部分 json 层次结构以进行延迟解析?
我目前的解决方案是,如有必要,使用 JsonDocument
缓存部分 json 以进行延迟解析。
我不喜欢的是我看不到在 JsonDocument
上调用 JsonSerializer
的方法所以我必须将它转换回带有 GetRawText()
的文本非常有效率。
public class MessageConverter : JsonConverter<Message>
{
public override bool CanConvert(Type objectType) =>
objectType == typeof(Message);
public override Message Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var message = new Message();
JsonDocument cachedBody = null;
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject)
{
break;
}
if (reader.TokenType == JsonTokenType.PropertyName)
{
var propertyName = reader.GetString();
reader.Read();
switch (propertyName)
{
case "Type":
message.Type = reader.GetString();
break;
case "Body":
if (message.Type != null)
{
message.Body = message.Type switch
{
"A" => JsonSerializer.Deserialize<BodyA>(ref reader, options),
"B" => JsonSerializer.Deserialize<BodyB>(ref reader, options),
_ => throw new Exception($"Cannot parse message body of type {message.Type}")
};
}
else
{
cachedBody = JsonDocument.ParseValue(ref reader);
}
break;
}
}
}
if (message.Body == null)
{
if (cachedBody == null)
{
throw new Exception($"Missing message body");
}
try
{
Log.Write("using cache");
message.Body = message.Type switch
{
"A" => JsonSerializer.Deserialize<BodyA>(cachedBody.RootElement.GetRawText()),
"B" => JsonSerializer.Deserialize<BodyB>(cachedBody.RootElement.GetRawText()),
_ => throw new Exception($"Cannot parse message body of type {message.Type}")
};
}
finally
{
cachedBody.Dispose();
}
}
return message;
}
public override void Write(Utf8JsonWriter writer, Message value, JsonSerializerOptions options)
{
writer.WriteStartObject();
writer.WritePropertyName("Type");
writer.WriteStringValue(value.Type);
writer.WritePropertyName("Body");
JsonSerializer.Serialize<object>(writer, value.Body, options);
writer.WriteEndObject();
}
}
文档中的 dotnet 示例:
显示手动解析每个 属性 多态类型。然而:
- 我的多态对象是复杂的深层层次结构,我无法手动编写每个字段的代码,因此我需要调用
JsonSerializer
. - 类型的线索在同级字段中指定。由于无法保证 json 元素顺序,因此
Utf8JsonReader
在遇到多态类型之前可能没有读取类型信息。
例如
[JsonConverter(typeof(MessageConverter))]
public class Message
{
public string Type { get; set; } // indicates what implementation IBody is
public IBody Body { get; set; }
}
public interface IBody
{
}
public class BodyA : IBody
{
// a big object hierarchy but just showing one property for simplicity
public string A { get; set; }
}
public class BodyB : IBody
{
// a big object hierarchy but just showing one property for simplicity
public string B { get; set; }
}
public class MessageConverter : JsonConverter<Message>
{
public override bool CanConvert(Type objectType) =>
objectType == typeof(Message);
public override Message Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var message = new Message();
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject)
{
break;
}
if (reader.TokenType == JsonTokenType.PropertyName)
{
var propertyName = reader.GetString();
reader.Read();
switch (propertyName)
{
case "Type":
message.Type = reader.GetString();
break;
case "Body":
// Body might be read before "Message.Type" so can't parse it yet
message.Body = /* help - what am I? */;
break;
}
}
}
return message;
}
public override void Write(Utf8JsonWriter writer, Message value, JsonSerializerOptions options)
throw new NotImplementedException();
}
正在查看Utf8JsonReader
:
- 有没有办法查看未来的元素或将解析器位置后移?
- 是否有有效的方法来缓存部分 json 层次结构以进行延迟解析?
我目前的解决方案是,如有必要,使用 JsonDocument
缓存部分 json 以进行延迟解析。
我不喜欢的是我看不到在 JsonDocument
上调用 JsonSerializer
的方法所以我必须将它转换回带有 GetRawText()
的文本非常有效率。
public class MessageConverter : JsonConverter<Message>
{
public override bool CanConvert(Type objectType) =>
objectType == typeof(Message);
public override Message Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var message = new Message();
JsonDocument cachedBody = null;
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject)
{
break;
}
if (reader.TokenType == JsonTokenType.PropertyName)
{
var propertyName = reader.GetString();
reader.Read();
switch (propertyName)
{
case "Type":
message.Type = reader.GetString();
break;
case "Body":
if (message.Type != null)
{
message.Body = message.Type switch
{
"A" => JsonSerializer.Deserialize<BodyA>(ref reader, options),
"B" => JsonSerializer.Deserialize<BodyB>(ref reader, options),
_ => throw new Exception($"Cannot parse message body of type {message.Type}")
};
}
else
{
cachedBody = JsonDocument.ParseValue(ref reader);
}
break;
}
}
}
if (message.Body == null)
{
if (cachedBody == null)
{
throw new Exception($"Missing message body");
}
try
{
Log.Write("using cache");
message.Body = message.Type switch
{
"A" => JsonSerializer.Deserialize<BodyA>(cachedBody.RootElement.GetRawText()),
"B" => JsonSerializer.Deserialize<BodyB>(cachedBody.RootElement.GetRawText()),
_ => throw new Exception($"Cannot parse message body of type {message.Type}")
};
}
finally
{
cachedBody.Dispose();
}
}
return message;
}
public override void Write(Utf8JsonWriter writer, Message value, JsonSerializerOptions options)
{
writer.WriteStartObject();
writer.WritePropertyName("Type");
writer.WriteStringValue(value.Type);
writer.WritePropertyName("Body");
JsonSerializer.Serialize<object>(writer, value.Body, options);
writer.WriteEndObject();
}
}