如何基于NewtonSoft.JSON与JSON Schema动态生成合规参考JSON数据
数组分支的层级传导错误
原代码的数组分支处理中,完成空数组实例创建后,遍历数组项Schema的属性时,直接将最外层的根jObject作为容器传入递归方法,没有将生成的属性绑定到当前构造的数组、数组元素对象上。这是嵌套属性被平铺到根节点的直接原因:递归调用没有沿着JSON的层级结构传递正确的父级容器,所有遍历到的数组属性都会被错误挂载到最外层节点。
这种缺陷在测试阶段的简单字符串数组中不会暴露,一旦数组元素是对象类型,立刻会出现结构错位。
对象分支的错位传导放大问题
原代码中对象分支的处理逻辑本身是正确的:创建独立嵌套对象、遍历属性递归构造、将对象绑定到父节点。但因为数组分支的传导错误,后续调用链中传入的容器已经不是预期的嵌套对象,最终输出结构会和预期的嵌套层级完全脱节,甚至出现空对象嵌套的冗余双大括号结构。
默认值分支的类型不兼容问题
原代码默认直接取jsonProperty.Value.Default,当Schema未定义默认值时,返回的null值很可能不符合属性的类型约束(比如数字、布尔、字符串类型的属性不允许为null),生成的JSON可能无法通过Schema校验。
Newtonsoft.Json.Schema没有提供开箱即用的“基于Schema动态生成合规示例JSON”的内置API,需要基于JSchema的结构递归构造,核心原则是每一层递归独立生成对应结构的JToken实例,由上层逻辑负责将实例绑定到正确的父节点,全程不跨层级传递容器。
修正后的递归方法:
static JToken GenerateTokenBySchema(JSchema schema) { switch (schema.Type) { // 处理数组类型 case JSchemaType.Array: JArray targetArray = new JArray(); foreach (var itemSchema in schema.Items) { // 递归生成每个数组元素的独立Token,直接追加到数组中 targetArray.Add(GenerateTokenBySchema(itemSchema)); } return targetArray; // 处理对象类型 case JSchemaType.Object: JObject targetObject = new JObject(); foreach (var property in schema.Properties) { // 递归生成每个属性的Token,绑定到当前嵌套对象 targetObject.Add(new JProperty(property.Key, GenerateTokenBySchema(property.Value))); } return targetObject; // 处理基础值类型 default: // 优先使用Schema定义的默认值 if (schema.Default != null) { return JToken.FromObject(schema.Default); } // 无默认值时按类型生成合规兜底值,避免null引发类型校验失败 return schema.Type switch { JSchemaType.String => string.Empty, JSchemaType.Integer => 0, JSchemaType.Number => 0.0, JSchemaType.Boolean => false, _ => JValue.CreateNull() }; } }
对应的调用逻辑可以简化为:
JSchema schema = JSchema.Parse(jsonSchema); // 根节点直接根据Schema生成,无需手动遍历根属性 JObject rootResult = (JObject)GenerateTokenBySchema(schema);
- 嵌套属性平铺问题:修正后所有递归分支都独立生成当前层级的Token,再由上层绑定,不会出现跨层级挂载属性的问题,
dimensions这类嵌套对象的属性会严格封装在对应节点内部。 - 冗余双大括号问题:数组元素会独立生成完整的对象结构,不会因为容器错位出现空的嵌套对象。
- 未知Schema适配:这套逻辑不需要提前知晓Schema结构,支持任意层级的嵌套对象、任意元素类型的数组构造,完全适配生产环境的动态解析场景。
扩展提示:如果需要兼容复杂Schema规则,比如枚举值匹配、必填字段强制输出、OneOf/AnyOf多类型联合判断,只需要在
GenerateTokenBySchema方法中补充对应的分支逻辑即可,核心的递归构造框架不需要调整。
内容的提问来源于stack exchange,提问作者Jeff Patton

