Deserialize complex polymorphic types with System.Text.Json

老子叫甜甜 提交于 2021-02-11 13:39:15

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!