You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在System.Text.Json自定义JsonConverter中解决复杂类型循环引用序列化问题

自定义JsonConverter循环引用问题解决方案

问题原因

该报错的核心原因是自定义JsonConverter的手写序列化逻辑完全绕开了System.Text.Json内置的引用追踪流程:ReferenceHandler.Preserve的引用校验、$id/$ref生成逻辑只会在内置的默认序列化流程中触发,你手动调用WriteStartObject、写入属性的操作不会触发这部分逻辑,因此即便配置了ReferenceHandler.Preserve也无法识别循环引用。

解决方案

方案1:使用内置属性过滤机制替代自定义Converter(最稳妥,兼容所有版本)

如果你的需求仅为固定忽略指定属性,完全不需要实现自定义Converter,直接给要忽略的属性添加[JsonIgnore]特性即可,这样所有内置的引用处理、序列化逻辑都能正常生效,不会出现循环引用问题:

public class User
{
    public string Name { get; set; }
    [JsonIgnore]
    public int Age { get; set; }
    public User Reference { get; set; }
}

序列化时仅需保留ReferenceHandler.Preserve配置,不需要注册自定义Converter,即可正常序列化带自引用的对象。
如果需要动态过滤属性,.NET 6及以上版本可以使用自定义JsonTypeInfo修饰器实现,同样不需要编写自定义Converter,可复用全部内置序列化能力。

方案2:自定义Converter内部手动维护引用追踪

如果必须使用自定义Converter实现特殊序列化逻辑,可以在Converter内部手动维护引用缓存,模拟内置的$id/$ref逻辑,适配ReferenceHandler.Preserve的输出格式:

public class UserJsonConverter : JsonConverter<User>
{
    // 使用ThreadStatic避免多线程场景下引用缓存冲突
    [ThreadStatic]
    private static Dictionary<User, string> _referenceCache;
    [ThreadStatic]
    private static int _referenceIdCounter;

    private void InitCacheIfNeeded()
    {
        if (_referenceCache == null)
        {
            _referenceCache = new Dictionary<User, string>();
            _referenceIdCounter = 1;
        }
    }

    public override User Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        throw new NotImplementedException();
    }

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

        // 处理已有引用的对象,直接输出$ref
        if (_referenceCache.TryGetValue(value, out var existingRefId))
        {
            writer.WriteString("$ref", existingRefId);
            writer.WriteEndObject();
            return;
        }

        // 新对象生成唯一$id
        var newId = _referenceIdCounter.ToString();
        _referenceCache.Add(value, newId);
        _referenceIdCounter++;
        writer.WriteString("$id", newId);

        // 写入自定义业务属性
        writer.WriteString(nameof(value.Name), value.Name);
        writer.WritePropertyName(nameof(value.Reference));
        JsonSerializer.Serialize(writer, value.Reference, options);

        writer.WriteEndObject();

        // 根对象序列化完成后清空缓存,避免内存泄漏
        if (_referenceCache.Count == 1)
        {
            _referenceCache.Clear();
            _referenceIdCounter = 1;
        }
    }
}

该实现完全兼容ReferenceHandler.Preserve的输出格式,可正确处理自引用、循环引用场景,不会再抛出循环引用异常。


内容的提问来源于stack exchange,提问作者Alexander Sheremetyev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.28 23:06:03