首页 > 解决方案 > 使用 System.Text.Json.Serialization.JsonConverter 解决 JSON 序列化过程中复杂类型的循环引用

问题描述

有一个复杂类型引用了相同类型的对象(有时是相同的对象):

public class User
{
    public string Name { get; set; }

    public int Age { get; set; }

    public User Reference { get; set; }
}

有一个自定义的 JsonConverter (System.Text.Json.Serialization) 实现来反序列化这个对象,避免一些特殊的属性。

public class UserJsonConverter : JsonConverter<User>
{
    public override User Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        throw new NotImplementedException();
    }

    public override void Write(Utf8JsonWriter writer, User value, JsonSerializerOptions options)
    {
        writer.WriteStartObject();

        // write the name property only and ignore the age
        writer.WriteString(nameof(value.Name), value.Name);

        writer.WritePropertyName(nameof(value.Reference));
        JsonSerializer.Serialize(writer, value.Reference, options);

        writer.WriteEndObject();
    }
}

但是在对象指向自身的情况下如何配置引用解析尚不清楚。例子:

var user = new User
{
    Age = 10,
    Name = "username"
};

user.Reference = user;

var options = new JsonSerializerOptions();
options.ReferenceHandler = ReferenceHandler.Preserve;
options.Converters.Add(new UserJsonConverter());

var result = JsonSerializer.Serialize(user, user.GetType(), options);

发生异常:

System.Text.Json.JsonException. A possible object cycle was detected. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 64. Consider using ReferenceHandler.Preserve on JsonSerializerOptions to support cycles

我们使用 System.Text.Json,版本=5.0.0.0

默认对象转换器的所有基于 '$ref' 和 '$id' 的方法都是内部的,无法使用。我看到的唯一方法是在序列化之前在某些 DTO 中简化 User 对象,并在完全没有此自定义转换器的情况下使用它。

但也许有人知道在自定义 JsonConverter 中是否有解决这些引用的正确方法?

标签: .netserializationsystem.text.jsonjsonserializerjsonconverter

解决方案


基于自定义引用处理程序使用的解决方案

UserJsonConverter 更改:

public override void Write(Utf8JsonWriter writer, User value, JsonSerializerOptions options)
{
    // use reference handler manually
    var v_resolver = options.ReferenceHandler?.CreateResolver();
    if (v_resolver != null)
    {
        var v_refID = v_resolver.GetReference(value, out bool alreadyExists);
        if (alreadyExists)
        {
            writer.WriteStartObject();
            writer.WriteString(JsonEncodedText.Encode("$ref", encoder: null), v_refID);
            writer.WriteEndObject();
            return;
        }
        else
        {
            writer.WriteStartObject();
            writer.WriteString(JsonEncodedText.Encode("$id", encoder: null), v_refID);
        }
    }

    //writer.WriteStartObject();

    ...
}

使用变化:

...
var options = new JsonSerializerOptions();
options.ReferenceHandler = new CutomReferenceHandler();
...

自定义参考处理程序实现可在此处获得:https ://github.com/dotnet/docs/issues/21777#issuecomment-736751404 或文档

它可以工作,但我有点害怕以这种方式使用它,因为将来可能会更改内部引用解析器逻辑($id,$refs)。


推荐阅读