JsonSchema.Net可空属性验证误报的排查与过滤咨询
我正在使用JsonSchema.Net验证JSON文档与预设Schema的一致性。部分属性在Schema中通过oneOf关键字定义为可空类型(格式如{"oneOf":[{"type":"string"},{"type":"null"}]})。但验证时,这些可空属性的部分评估结果会返回IsValid=false,错误信息与实际JSON值相悖(例如“值为"null",但应为"integer"”或“值为"number",但应为"null"”)。我想知道问题原因,以及如何过滤此类误报,以便快速定位真正的错误节点。
文档示例
{ "referenceNumber": "35366", "storageZone": null, "maxCount": null, "length": 124.5 }
对应的JSON Schema
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Schema Example", "$defs": { "stringNullable": { "oneOf": [ { "type": "string" }, { "type": "null" } ] }, "integerNullable": { "oneOf": [ { "type": "integer" }, { "type": "null" } ] }, "numberNullable": { "oneOf": [ { "type": "number" }, { "type": "null" } ] } }, "type": "object", "properties": { "referenceNumber": { "type": "string" }, "storageZone": { "$ref": "#/$defs/stringNullable" }, "maxCount": { "$ref": "#/$defs/integerNullable" }, "length": { "$ref": "#/$defs/numberNullable" } } }
代码示例
[Test] public void JsonSchemaAsserts() { var schema = JsonSchema.FromFile("./SchemaExample.json"); var jsonText = File.ReadAllText("./DataExample.json"); var json = JsonNode.Parse(jsonText); var validationResult = schema.Evaluate(json, new EvaluationOptions() { OutputFormat = OutputFormat.List }); Assert.That(validationResult.IsValid, Is.True); var validationErrors = validationResult.Details.Where(d => !d.IsValid && d.HasErrors).ToList(); Assert.That(validationResult, Is.Empty); }
这是oneOf关键字的特性导致的——oneOf要求恰好有一个子Schema验证通过。验证可空属性时,JsonSchema.Net会对oneOf下的每个子Schema分别评估,每个子Schema的失败结果都会被记录到Details集合中,但最终整个oneOf的验证结果是通过的(因为有一个子Schema匹配成功)。你看到的“误报”其实是单个子Schema的失败信息,而非整个属性的最终验证结果。
比如maxCount为null时,integer类型的子Schema验证失败,会生成错误信息,但null类型的子Schema验证通过,所以整个oneOf的结果是有效的,只是Details里包含了子Schema的失败记录。
1. 修正错误过滤逻辑
要过滤掉这些子Schema的失败信息,只保留真正的错误,需要判断错误是否来自最终验证失败的节点。修改代码中的错误筛选逻辑,只保留那些自身验证失败且父节点也验证失败的错误:
[Test] public void JsonSchemaAsserts() { var schema = JsonSchema.FromFile("./SchemaExample.json"); var jsonText = File.ReadAllText("./DataExample.json"); var json = JsonNode.Parse(jsonText); var validationResult = schema.Evaluate(json, new EvaluationOptions() { OutputFormat = OutputFormat.List }); Assert.That(validationResult.IsValid, Is.True); // 过滤仅保留真正的错误节点:自身验证失败,且父节点也未通过验证 var validationErrors = validationResult.Details .Where(d => !d.IsValid && d.HasErrors && (d.Parent == null || !d.Parent.IsValid)) .ToList(); Assert.That(validationErrors, Is.Empty); }
2. 使用简化的可空类型定义(推荐)
从JSON Schema Draft 2019-09开始,支持直接通过type数组定义可空类型,无需使用oneOf。这种方式的验证不会产生子节点的失败记录,结果更简洁:
修改Schema中的可空定义:
"$defs": { "stringNullable": { "type": ["string", "null"] }, "integerNullable": { "type": ["integer", "null"] }, "numberNullable": { "type": ["number", "null"] } }
这样修改后,Details集合中只会保留真正的验证失败信息,无需额外过滤。
内容的提问来源于stack exchange,提问作者GKalnytskyi

