如何解决自定义OpenApiSchema生成器的递归栈溢出等问题
解决自定义OpenApiSchema生成器的三个核心问题
1. 解决同一类型OpenApiSchema重复生成的问题
核心思路是维护一个类型-Schema/引用的缓存字典,确保每个类型仅生成一次Schema,后续直接复用已有实例或引用:
// 缓存已处理类型对应的Schema,避免重复生成 private readonly Dictionary<Type, OpenApiSchema> _schemaCache = new(); public OpenApiSchema GenerateSchema(Type type) { // 先检查缓存,存在则直接返回 if (_schemaCache.TryGetValue(type, out var existingSchema)) { return existingSchema; } // 初始化当前类型的Schema并存入缓存,提前占位 var schema = new OpenApiSchema(); _schemaCache[type] = schema; // 填充Schema的属性、类型、特性元数据等逻辑 // ... return schema; }
2. 解决循环引用导致的栈溢出问题
结合上述缓存机制,在递归处理属性时,若遇到已存入缓存(即使未完成生成)的类型,直接返回OpenApiReference而非继续递归,打断循环链路:
public OpenApiSchema GenerateSchema(Type type) { if (_schemaCache.TryGetValue(type, out var existingSchema)) { // 若Schema未完成填充(循环引用场景),返回引用占位 if (existingSchema.Properties == null || !existingSchema.Properties.Any()) { return new OpenApiSchema { Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = type.Name // 用类型名作为Schema的唯一标识 } }; } return existingSchema; } var schema = new OpenApiSchema { Type = "object", Properties = new Dictionary<string, OpenApiSchema>() }; _schemaCache[type] = schema; // 遍历属性并递归生成Schema foreach (var prop in type.GetProperties()) { var propSchema = GenerateSchema(prop.PropertyType); schema.Properties.Add(prop.Name, propSchema); } // 给当前Schema设置引用标识,供其他类型引用 schema.Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = type.Name }; return schema; }
关键逻辑是先缓存占位,再递归处理属性,循环引用时会直接返回引用,避免无限递归导致栈溢出。
3. 优化递归代码的可读性
将大段递归逻辑拆分为多个单一职责的方法,避免嵌套过深,也可以用迭代方式替代递归:
拆分单一职责方法
// 入口方法 public OpenApiSchema GenerateSchema(Type type) { if (_schemaCache.TryGetValue(type, out var existing)) return GetSchemaOrReference(existing); var schema = CreateBaseSchema(type); _schemaCache[type] = schema; PopulateSchemaProperties(schema, type); ApplyAttributeMetadata(schema, type); // 处理ApiCommands、ApiArgument等特性 return schema; } // 判断返回完整Schema还是引用占位 private OpenApiSchema GetSchemaOrReference(OpenApiSchema schema) { return schema.Properties?.Any() ?? false ? schema : new OpenApiSchema { Reference = schema.Reference }; } // 创建基础Schema结构(区分值类型/引用类型) private OpenApiSchema CreateBaseSchema(Type type) { if (type.IsValueType || type == typeof(string)) { return new OpenApiSchema { Type = MapToOpenApiType(type) }; } return new OpenApiSchema { Type = "object", Properties = new Dictionary<string, OpenApiSchema>() }; } // 填充类型的属性Schema private void PopulateSchemaProperties(OpenApiSchema schema, Type type) { foreach (var prop in type.GetProperties()) { var propSchema = GenerateSchema(prop.PropertyType); schema.Properties.Add(prop.Name, propSchema); } } // 处理ApiCommands/ApiArgument等特性的元数据 private void ApplyAttributeMetadata(OpenApiSchema schema, Type type) { var apiCommandAttr = type.GetCustomAttribute<ApiCommandsAttribute>(); if (apiCommandAttr != null) { schema.Description = apiCommandAttr.Description; } // 处理其他特性逻辑 }
用迭代替代递归(可选)
如果递归层级过深,可改用队列/栈遍历类型,进一步降低栈溢出风险并提升可读性:
public OpenApiSchema GenerateSchema(Type rootType) { var processQueue = new Queue<(Type Type, OpenApiSchema Schema)>(); var rootSchema = CreateBaseSchema(rootType); _schemaCache[rootType] = rootSchema; processQueue.Enqueue((rootType, rootSchema)); while (processQueue.Count > 0) { var (currentType, currentSchema) = processQueue.Dequeue(); foreach (var prop in currentType.GetProperties()) { var propType = prop.PropertyType; if (!_schemaCache.TryGetValue(propType, out var propSchema)) { propSchema = CreateBaseSchema(propType); _schemaCache[propType] = propSchema; processQueue.Enqueue((propType, propSchema)); } currentSchema.Properties.Add(prop.Name, GetSchemaOrReference(propSchema)); } ApplyAttributeMetadata(currentSchema, currentType); } return rootSchema; }
内容的提问来源于stack exchange,提问作者Anthony
相关产品推荐
相关产品推荐

