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

.NET 6自定义JsonConverter序列化触发StackOverflowException如何解决?

.NET 6 Web API 自定义Json转换器递归栈溢出问题解决

问题描述

在.NET 6 Web API中,为支持多类型值的JSON对象编写了自定义转换器MyConverter,并在Program.cs中完成注册:

builder.Services.AddControllers(opt => opt.Conventions.Add(new RouteTokenTransformerConvention(new SlugifyParameterTransformer())))
    .AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
        options.JsonSerializerOptions.Converters.Add(new MyConverter());
        options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
        options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
        options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
    });

当在转换器的Write方法中调用Serialize时:

public override void Write(Utf8JsonWriter writer, Quota value, JsonSerializerOptions options)
{
    JsonSerializer.Serialize(writer, value, options);
}

会抛出StackOverflowException,原因是传入的options参数包含MyConverter的引用,导致递归调用。若创建不含该转换器的新JsonSerializerOptions对象可解决问题,但违反DRY原则。

补充堆栈跟踪信息(重复片段):

at System.Text.Json.Serialization.JsonConverter`1[[System.__Canon, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].TryWrite(System.Text.Json.Utf8JsonWriter, System.__Canon ByRef, System.Text.Json.JsonSerializerOptions, System.Text.Json.WriteStack ByRef)
   at System.Text.Json.Serialization.JsonConverter`1[[System.__Canon, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].WriteCore(System.Text.Json.Utf8JsonWriter, System.__Canon ByRef, System.Text.Json.JsonSerializerOptions, System.Text.Json.WriteStack ByRef)
   at System.Text.Json.JsonSerializer.WriteUsingSerializer[[System.__Canon, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]](System.Text.Json.Utf8JsonWriter, System.__Canon ByRef, System.Text.Json.Serialization.Metadata.JsonTypeInfo)
   at System.Text.Json.JsonSerializer.Serialize[[System.__Canon, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]](System.Text.Json.Utf8JsonWriter, System.__Canon, System.Text.Json.JsonSerializerOptions)
   at MyProject.Infrastructure.Shared.MyConverter.Write(System.Text.Json.Utf8JsonWriter, MyProject.Domain.Shared.Quota, System.Text.Json.JsonSerializerOptions)
   ...

SlugifyParameterTransformer类代码:

public sealed class SlugifyParameterTransformer : IOutboundParameterTransformer
{
    public string? TransformOutbound(object? value)
    {
        if (value is not string) { return null; }

        // Slugify value
        return Regex.Replace((value as string)!, "([a-z])([A-Z])", "$1-$2").ToLower();
    }
}

解决方法

以下几种方案可避免递归栈溢出,同时遵循DRY原则:

方案1:临时移除当前转换器再恢复

在Write方法中临时从配置中移除MyConverter,完成序列化后再恢复,确保复用原配置的所有设置:

public override void Write(Utf8JsonWriter writer, Quota value, JsonSerializerOptions options)
{
    var converter = options.Converters.FirstOrDefault(c => c is MyConverter);
    if (converter != null)
    {
        options.Converters.Remove(converter);
        try
        {
            JsonSerializer.Serialize(writer, value, options);
        }
        finally
        {
            // 确保转换器一定会被恢复,不影响后续操作
            options.Converters.Add(converter);
        }
    }
    else
    {
        JsonSerializer.Serialize(writer, value, options);
    }
}

方案2:预创建排除当前转换器的配置副本

在转换器初始化时,基于原配置创建一个移除自身的副本,后续序列化复用该副本,避免重复创建配置对象:

首先修改MyConverter的构造函数:

public class MyConverter : JsonConverter<Quota>
{
    private readonly JsonSerializerOptions _optionsWithoutSelf;

    public MyConverter(JsonSerializerOptions originalOptions)
    {
        // 复制原配置的所有规则
        _optionsWithoutSelf = new JsonSerializerOptions(originalOptions);
        // 移除当前转换器
        _optionsWithoutSelf.Converters.RemoveAll(c => c is MyConverter);
    }

    public override void Write(Utf8JsonWriter writer, Quota value, JsonSerializerOptions options)
    {
        // 使用预创建的无自身的配置序列化
        JsonSerializer.Serialize(writer, value, _optionsWithoutSelf);
    }

    // 实现Read等其他抽象方法...
}

然后在Program.cs注册时传入原配置:

.AddJsonOptions(options =>
{
    options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
    // 传入当前配置初始化转换器
    options.JsonSerializerOptions.Converters.Add(new MyConverter(options.JsonSerializerOptions));
    options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
    options.JsonSerializerOptions.DictionaryKeyPolicy = JsonNamingPolicy.CamelCase;
    options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
});

方案3:使用JsonTypeInfo绕过转换器

利用.NET 6+的JsonTypeInfo特性,临时替换类型的转换器为默认实现,完成序列化后恢复:

public override void Write(Utf8JsonWriter writer, Quota value, JsonSerializerOptions options)
{
    var typeInfo = options.GetTypeInfo(typeof(Quota)) as JsonTypeInfo<Quota>;
    if (typeInfo != null)
    {
        var originalConverter = typeInfo.Converter;
        // 临时使用默认序列化逻辑
        typeInfo.Converter = null;
        try
        {
            JsonSerializer.Serialize(writer, value, typeInfo);
        }
        finally
        {
            // 恢复原转换器
            typeInfo.Converter = originalConverter;
        }
    }
    else
    {
        JsonSerializer.Serialize(writer, value, options);
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 06:47:49