使用 System.Text.Json 反序列化复杂的多态类型 [英] Deserialize complex polymorphic types with System.Text.Json

查看:21
本文介绍了使用 System.Text.Json 反序列化复杂的多态类型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

文档中的 dotnet 示例:

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:

  • 我的多态对象是复杂的深层层次结构,我无法手动编写每个字段的代码,因此我需要调用 JsonSerializer.
  • 类型的线索在同级字段中指定.鉴于无法保证 json 元素顺序,Utf8JsonReader 在遇到多态类型之前可能没有读取类型信息.
  • 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.

例如

[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 以进行延迟解析.

The current solution I have is, if necessary, use a JsonDocument to cache part of the json for deferred parsing.

我不喜欢的是我看不到在 JsonDocument 上调用 JsonSerializer 的方法,所以我必须使用 将其转换回文本GetRawText() 效率不高.

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();
    }
}

这篇关于使用 System.Text.Json 反序列化复杂的多态类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆