如何使用JSchema动态校验扁平化JSON属性后生成合规JObject
实现通用扁平化JSON属性校验方案
你不需要先拼装完整JObject再执行校验,直接通过属性路径定位到JSchema中对应节点的约束,单条属性逐条校验即可,还能提前发现错误,不用等全量属性收集完成再处理。
核心实现步骤
- 第一步:预加载JSchema并构建路径映射表
先把从文件读取的JSchema解析为JSchema对象,递归遍历整个Schema结构,把所有合法的JSON路径和对应的Schema约束(类型、格式、枚举、范围等)存入字典,键统一使用点分隔的路径字符串,和你收到的属性路径格式完全匹配。
注意处理数组类型的Schema:如果路径中包含数组项,比如UniversityCollection.Persons[*].Name,要把动态索引(比如Person1、Person2这类自定义标识)替换成通配符匹配规则,只要路径符合「前缀+索引后缀+后续路径」的格式,就匹配对应数组项的Schema约束。 - 第二步:单条属性实时校验
每条扁平化属性收到后直接执行以下操作:- 拆分属性字符串,得到
属性路径和属性值两部分 - 用属性路径去提前构建的Schema路径映射表里匹配对应的约束规则:
- 匹配不到直接报错:该属性不符合Schema定义,属于非法字段
- 匹配到的话,把属性值转换成Schema要求的类型(比如字符串转数字、布尔值、日期等),再校验是否符合约束(比如长度、数值范围、枚举值、格式要求等)
- 校验不通过直接抛出对应错误信息,包含具体属性路径、错误值、错误原因
- 拆分属性字符串,得到
- 第三步:全量校验兜底
等所有属性都校验完成、拼装成JObject之后,再用JSchema做一次全量整体校验,补漏单条属性校验覆盖不到的规则:比如必填字段缺失、字段之间的依赖约束、Schema的if/then/else逻辑等。
核心代码示例(基于Newtonsoft.Json.Schema)
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Schema; using System.Text.RegularExpressions; // 全局Schema路径映射表 Dictionary<string, JSchema> schemaPathMap = new Dictionary<string, JSchema>(); // 构建Schema路径映射的递归方法 void BuildSchemaPathMap(string currentPath, JSchema currentSchema) { schemaPathMap[currentPath] = currentSchema; // 遍历对象属性 foreach (var prop in currentSchema.Properties) { string childPath = string.IsNullOrEmpty(currentPath) ? prop.Key : $"{currentPath}.{prop.Key}"; BuildSchemaPathMap(childPath, prop.Value); } // 处理数组项约束 if (currentSchema.Items.Any()) { string arrayItemPath = $"{currentPath}[*]"; foreach (var itemSchema in currentSchema.Items) { BuildSchemaPathMap(arrayItemPath, itemSchema); } } } // 单条属性校验方法 bool ValidateSingleProperty(string propPath, string propValue, out string errorMsg) { errorMsg = string.Empty; // 动态索引替换为通配符匹配Schema路径,可根据你的路径格式调整正则规则 string matchPath = Regex.Replace(propPath, @"\.\w+\d+", match => $".[*]"); if (!schemaPathMap.TryGetValue(matchPath, out var targetSchema)) { errorMsg = $"属性{propPath}不存在于Schema定义中"; return false; } // 转换值类型并校验约束 JToken valueToken; try { valueToken = JToken.FromObject(Convert.ChangeType(propValue, targetSchema.Type.Value)); } catch { errorMsg = $"属性{propPath}的值{propValue}类型不符合要求,要求类型为{targetSchema.Type}"; return false; } return valueToken.IsValid(targetSchema, out IList<string> errors); }
注意事项
- 如果你的Schema包含
additionalProperties: false约束,预构建映射表的时候要额外标记每个节点是否允许额外属性,匹配不到路径的时候先判断父节点是否允许额外属性,再决定是否报错。 - 数组索引如果是数字格式(比如
Persons[0].Name),正则匹配规则要对应调整适配你的路径格式。
内容的提问来源于stack exchange,提问作者Enrique González
相关产品推荐
相关产品推荐

