问题
The dotnet example in the documentation:
https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-converters-how-to#support-polymorphic-deserialization
shows manually parsing each property of a polymorphic type. However:
- my polymorphic objects are complex deep hierarchies, I can't hand code every field so I need to invoke the
JsonSerializer
. - the clue for the type is specified in sibling fields. Given there is no guarantee about json element order, a
Utf8JsonReader
may not have read the type information before it encounters the polymorphic type.
e.g.
[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();
}
Looking at Utf8JsonReader
:
- Is there a way to peek at future elements or move the parser position back?
- Is there an efficient way to cache part of the json hierarchy for deferred parsing?
回答1:
The current solution I have is, if necessary, use a JsonDocument
to cache part of the json for deferred parsing.
I don't like is that I can't see a way to invoke JsonSerializer
on a JsonDocument
so I have to convert it back to text with GetRawText()
which won't be very efficient.
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();
}
}
来源:https://stackoverflow.com/questions/62949042/deserialize-complex-polymorphic-types-with-system-text-json