.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
相关产品推荐
相关产品推荐

