.NET 6中如何从动态JSON生成OpenApiSchema并对接Swashbuckle?
.NET 6 动态生成 OpenApiSchema 实现思路
1. 核心依赖选型
直接使用官方的Microsoft.OpenApi库构建OpenAPI规范对象(Swashbuckle底层依赖此库,天然兼容OpenAPI V3标准),搭配.NET 6原生的System.Text.Json解析JSON负载,避免引入第三方工具,保证轻量和性能。
2. 递归解析JSON生成OpenApiSchema
编写递归方法遍历JSON节点,将JSON类型映射为对应的OpenApiSchema类型,同时处理嵌套对象、数组、格式推断等场景:
- 基础类型映射:字符串/数字/布尔值直接对应
OpenApiSchema.Type,字符串值若符合UUID、日期等格式,自动添加Format属性 - 对象类型:创建
object类型的Schema,递归处理每个属性,收集必填字段(可根据业务规则,比如非null属性标记为必填),并将嵌套对象提取为独立Schema加入components.schemas,用引用关联 - 数组类型:创建
array类型的Schema,递归解析数组元素得到Items属性 - Null值处理:设置
Nullable = true,同时可根据上下文推断基础类型(如默认设为string)
示例核心递归代码片段:
private OpenApiSchema GenerateSchemaFromJsonElement(JsonElement element, Dictionary<string, OpenApiSchema> componentsSchemas, ref string schemaName) { var schema = new OpenApiSchema(); switch (element.ValueKind) { case JsonValueKind.String: schema.Type = "string"; if (Guid.TryParse(element.GetString(), out _)) schema.Format = "uuid"; // 可扩展日期、邮箱等格式判断逻辑 break; case JsonValueKind.Number: schema.Type = element.TryGetInt64(out _) ? "integer" : "number"; break; case JsonValueKind.True: case JsonValueKind.False: schema.Type = "boolean"; break; case JsonValueKind.Object: schema.Type = "object"; schema.Properties = new Dictionary<string, OpenApiSchema>(); schema.Required = new HashSet<string>(); schema.AdditionalProperties = new OpenApiSchema { Type = "boolean", Default = false }; // 禁止额外属性 foreach (var prop in element.EnumerateObject()) { var propSchema = GenerateSchemaFromJsonElement(prop.Value, componentsSchemas, ref schemaName); schema.Properties.Add(prop.Name, propSchema); if (prop.Value.ValueKind != JsonValueKind.Null) schema.Required.Add(prop.Name); // 提取嵌套对象为独立Schema if (prop.Value.ValueKind == JsonValueKind.Object) { var childSchemaName = PascalCase(prop.Name); if (!componentsSchemas.ContainsKey(childSchemaName)) componentsSchemas.Add(childSchemaName, propSchema); // 替换为引用 schema.Properties[prop.Name] = new OpenApiSchema { Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = childSchemaName } }; } } break; case JsonValueKind.Array: schema.Type = "array"; if (element.GetArrayLength() > 0) schema.Items = GenerateSchemaFromJsonElement(element.EnumerateArray().First(), componentsSchemas, ref schemaName); break; case JsonValueKind.Null: schema.Nullable = true; schema.Type = "string"; // 默认基础类型,可根据业务调整 break; } return schema; } // 辅助方法:蛇形/小写命名转大驼峰 private string PascalCase(string input) { return string.Concat(input.Split('_', '-').Select(word => char.ToUpper(word[0]) + word.Substring(1))); }
3. 构建完整OpenAPI规范
生成根Schema后,将其加入components.schemas,再构建Info、Paths、RequestBody等完整结构:
public OpenApiDocument GenerateOpenApiSpec(string jsonPayload, string apiPath, string operationId, string schemaName) { var doc = new OpenApiDocument { OpenApiVersion = "3.0.1", Info = new OpenApiInfo { Title = "Sample API Capabilities", Version = "v1" }, Paths = new OpenApiPaths(), Components = new OpenApiComponents { Schemas = new Dictionary<string, OpenApiSchema>() } }; // 解析JSON负载 var jsonDoc = JsonDocument.Parse(jsonPayload); var rootSchema = GenerateSchemaFromJsonElement(jsonDoc.RootElement, doc.Components.Schemas, ref schemaName); doc.Components.Schemas.Add(schemaName, rootSchema); // 构建POST接口 var postOperation = new OpenApiOperation { OperationId = operationId, Tags = new List<OpenApiTag> { new OpenApiTag { Name = "Dynamic Objects" } }, RequestBody = new OpenApiRequestBody { Content = new Dictionary<string, OpenApiMediaType> { ["application/json"] = new OpenApiMediaType { Schema = new OpenApiSchema { Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = schemaName } } }, ["text/json"] = new OpenApiMediaType { Schema = new OpenApiSchema { Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = schemaName } } }, ["application/*+json"] = new OpenApiMediaType { Schema = new OpenApiSchema { Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = schemaName } } } } }, Responses = new OpenApiResponses { ["200"] = new OpenApiResponse { Description = "Success" } } }; doc.Paths.Add(apiPath, new OpenApiPathItem { Post = postOperation }); return doc; }
4. 适配频繁修改的JSON负载
- 每次收到新JSON时,重新生成完整OpenAPI规范
- 用JSON内容的哈希值作为缓存键,缓存已生成的Schema和规范,避免重复计算
- 在递归方法中记录已处理的Schema名称,处理可能的循环引用
5. 集成Swashbuckle(可选)
实现IDocumentFilter,在Swagger文档生成时动态注入动态生成的路径和Schema:
public class DynamicSchemaDocumentFilter : IDocumentFilter { private readonly string _jsonPayload; private readonly string _apiPath; private readonly string _operationId; private readonly string _schemaName; public DynamicSchemaDocumentFilter(string jsonPayload, string apiPath, string operationId, string schemaName) { _jsonPayload = jsonPayload; _apiPath = apiPath; _operationId = operationId; _schemaName = schemaName; } public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) { var generator = new DynamicOpenApiGenerator(); var dynamicDoc = generator.GenerateOpenApiSpec(_jsonPayload, _apiPath, _operationId, _schemaName); // 合并到现有文档 foreach (var schema in dynamicDoc.Components.Schemas) swaggerDoc.Components.Schemas.TryAdd(schema.Key, schema.Value); foreach (var path in dynamicDoc.Paths) swaggerDoc.Paths.TryAdd(path.Key, path.Value); } }
注册到SwaggerGen:
builder.Services.AddSwaggerGen(c => { // 从业务数据源获取动态JSON var dynamicJson = "{...}"; c.DocumentFilter<DynamicSchemaDocumentFilter>(dynamicJson, "/api/DataRecord/UpsertDynamicObj12", "UpsertDynamicExample", "DynamicObj12"); });
内容的提问来源于stack exchange,提问作者Solo812
相关产品推荐
相关产品推荐

