Angular调用.NET Web API:多态JSON序列化反序列化及栈溢出问题
咱们先把你的问题拆解清楚:你需要在Angular客户端调用.NET Web API时,正确反序列化包含抽象类Detector(子类TextDetector、ColorDetector)集合的Model对象。最初用TypeNameHandling.Auto的方案因为客户端难以处理带$前缀的类型标记而放弃,后来尝试基于Color属性判断类型的自定义JsonConverter,却因为ReadJson方法里的JObject.Load(reader)触发无限循环导致栈溢出,同时还用到了Entity Framework的GetModel方法。
下面一步步给你解决这些问题:
一、先搞定栈溢出的核心问题
你遇到的栈溢出是因为自定义Converter的递归调用:当你在ReadJson里调用JObject.Load(reader)时,Json.NET会再次尝试用这个自定义Converter来处理Detector类型,形成无限循环。
修复方案:临时禁用自定义Converter读取JObject
在ReadJson方法中,创建一个排除当前Converter的JsonSerializer实例,用它来读取JObject,避免递归。这里给你写个完整的DetectorConverter示例:
public class DetectorConverter : JsonConverter { public override bool CanConvert(Type objectType) { return typeof(Detector).IsAssignableFrom(objectType); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { // 创建临时序列化配置,移除当前Converter避免递归 var tempSettings = new JsonSerializerSettings(); tempSettings.Converters.Remove(this); var tempSerializer = JsonSerializer.Create(tempSettings); // 用临时序列化器读取JObject var jObject = JObject.Load(reader, tempSerializer); // 根据Color属性判断具体子类 Detector detector = jObject.ContainsKey("color") ? new ColorDetector() : new TextDetector(); // 用原序列化器填充对象属性 serializer.Populate(jObject.CreateReader(), detector); return detector; } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { // 序列化时用默认逻辑即可,无需额外处理 serializer.Serialize(writer, value); } }
二、配置.NET Web API使用自定义Converter
接下来要把这个Converter配置到Web API的序列化设置里,同时替换掉原来的TypeNameHandling.Auto:
如果是.NET 6+(Program.cs)
builder.Services.AddControllers() .AddJsonOptions(options => { // 移除TypeNameHandling.Auto,使用默认配置即可 options.JsonSerializerOptions.Converters.Add(new DetectorConverter()); });
如果是旧版本(Startup.cs)
public void ConfigureServices(IServiceCollection services) { services.AddControllers() .AddNewtonsoftJson(options => { options.SerializerSettings.TypeNameHandling = TypeNameHandling.Object; options.SerializerSettings.Converters.Add(new DetectorConverter()); }); }
也可以直接在抽象类Detector上标记特性,配置更直观:
[JsonConverter(typeof(DetectorConverter))] public abstract class Detector { // 公共属性,比如Bounds public Bounds Bounds { get; set; } }
三、Entity Framework GetModel方法的注意事项
你的GetModel方法要确保正确加载Detectors集合,避免EF延迟加载导致序列化问题。比如:
public async Task<Model> GetModel(int id) { return await _context.Models .Include(m => m.Detectors) // 显式加载Detectors集合,避免延迟加载引发的序列化异常 .FirstOrDefaultAsync(m => m.Id == id); }
如果Bounds是复杂类型,也要确保它能被正常序列化,避免和自定义Converter产生冲突。
四、Angular客户端的处理
现在API返回的JSON不会带$type标记了,客户端可以直接根据color属性判断Detector类型。你可以这样定义TypeScript接口:
// 公共Detector接口 interface Detector { bounds: { x: number; y: number; width: number; height: number }; // 其他公共属性 } // TextDetector子类 interface TextDetector extends Detector { text: string; // TextDetector特有属性 } // ColorDetector子类 interface ColorDetector extends Detector { color: string; // ColorDetector特有属性 } // 反序列化时转换类型 function parseDetectors(detectors: any[]): (TextDetector | ColorDetector)[] { return detectors.map(det => { return det.color ? (det as ColorDetector) : (det as TextDetector); }); } // 调用API时使用 this.http.get<{ detectors: any[] }>('/api/models/1') .subscribe(response => { const parsedDetectors = parseDetectors(response.detectors); // 后续处理逻辑 });
这样客户端就能正确识别不同类型的Detector了。
内容的提问来源于stack exchange,提问作者Sebastien Servouze

