使用 System.Text.Json 反序列化复杂的多态类型

Deserialize complex polymorphic types with System.Text.Json

文档中的 dotnet 示例:

https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-converters-how-to#support-polymorphic-deserialization

显示手动解析每个 属性 多态类型。然而:

例如

[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

我目前的解决方案是,如有必要,使用 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();
    }
}