Json Schema简介和Json Schema的高性能.net实现库 LateApexEarlySpeed.Json.Schema

9,673次阅读
没有评论

共计 8517 个字符,预计需要花费 22 分钟才能阅读完成。

什么是 Json Schema?

Json schema 是一种声明式语言,它可以用来标识 Json 的结构,数据类型和数据的具体限制,它提供了描述期望 Json 结构的标准化方法。
利用 Json Schema, 你可以定义 Json 结构的各种规则,以便确定 Json 数据在各个子系统中交互传输时保持兼容和一致的格式。

一般来说,系统可以自己实现逻辑来判断当前 json 是否满足接口要求,比如是否某个字段存在,是否属性值是有效的。但当验证需求变得复杂后,比如有大量嵌套 json 结构,属性之间的复杂关联限制等等,则容易编写出考虑不全的验证代码。另外,当系统需要动态的 json 数据要求,比如先由用户自己决定他需要的 json 结构,然后系统根据用户表达的定制化 json 结构需求,帮助用户验证后续的 json 数据。这种系统代码编译时无法确定的 json 结构,就需要另一种解决方案。

Json Schema 就是针对这种问题的比较自然的解决方案。它可以让你或你的用户描述希望的 json 结构和值的内容限制,有效属性,是否是 required, 还有有效值的定义,等等。。利用 Json Schema, 人们可以更好的理解 Json 结构,而且程序也可以根据你的 Json Schema 验证 Json 数据。
Json Schema 语法的学习见官方介绍。

比如下面的一个简单例子,用.net 下的 Json Schema 实现库 library LateApexEarlySpeed.Json.Schema 进行 Json 数据的验证:

Json Schema(文件:schema.json):

{
  "type": "object",
  "properties": {
    "propBoolean": {"type": "boolean"},
    "propArray": {
      "type": "array",
      "uniqueItems": true
    }
  }
}

Json 数据(文件:instance.json):

{
  "propBoolean": true,
  "propArray": [1, 2, 3, 4, 4]
}

C# 代码:

            string jsonSchema = File.ReadAllText("schema.json");
            string instance = File.ReadAllText("instance.json");

            var jsonValidator = new JsonValidator(jsonSchema);
            ValidationResult validationResult = jsonValidator.Validate(instance);

            if (validationResult.IsValid)
            {
                Console.WriteLine("good");
            }
            else
            {
                Console.WriteLine($"Failed keyword: {validationResult.Keyword}");
                Console.WriteLine($"ResultCode: {validationResult.ResultCode}");
                Console.WriteLine($"Error message: {validationResult.ErrorMessage}");
                Console.WriteLine($"Failed instance location: {validationResult.InstanceLocation}");
                Console.WriteLine($"Failed relative keyword location: {validationResult.RelativeKeywordLocation}");
            }

输出:

Failed keyword: uniqueItems
ResultCode: DuplicatedArrayItems
Error message: There are duplicated array items
Failed instance location: /propArray
Failed relative keyword location: /properties/propArray/uniqueItems

LateApexEarlySpeed.Json.Schema 中文介绍

项目原始文档:https://github.com/lateapexearlyspeed/Lateapexearlyspeed.JsonSchema

中文文档:
LateApexEarlySpeed.Json.Schema 是 2023 年 12 月发布的一个新的.net 下的 Json Schema 实现库 library,基于截止到 2023 年 12 月为止最新版的 Json schema – draft 2020.12。
Json Schema 验证功能经过了 official json schema test-suite for draft 2020.12 的测试。(部分排除的用例见下面的已知限制章节)

主要特点:

  • 基于微软.net 下默认的 System.Text.Json 而非经典的 Newtonsoft.Json
  • 高性能:和已有的知名且杰出的.net 下的一些 JsonSchema library 相比,具有很好的性能 (在 common case 下,利用 BenchmarkDotnet 进行的性能测试)。用户请根据自己的使用场景进行性能验证

一些性能测试结果 (下面的测试比较都是在同样的使用方式下进行,见 性能建议):
12th Gen Intel Core i7-12800H, 1 CPU, 20 logical and 14 physical cores

[Host] : .NET 6.0.25 (6.0.2523.51912), X64 RyuJIT AVX2

DefaultJob : .NET 6.0.25 (6.0.2523.51912), X64 RyuJIT AVX2

有效数据用例:

Method Mean Error StdDev Gen0 Gen1 Allocated
ValidateByPopularSTJBasedValidator 29.80 us 0.584 us 0.573 us 4.4556 0.2441 55.1 KB
ValidateByThisValidator 15.99 us 0.305 us 0.300 us 1.9531 24.2 KB

无效数据用例:

Method Mean Error StdDev Median Gen0 Gen1 Allocated
ValidateByPopularSTJBasedValidator 65.04 us 2.530 us 7.341 us 66.87 us 4.5776 0.1221 56.42 KB
ValidateByThisValidator 15.47 us 1.160 us 3.421 us 17.14 us 1.4954 18.45 KB

Note: “STJ” 是 ”System.Text.Json” 的缩写,这个是.net sdk 里默认带的 json 基础包, LateApexEarlySpeed.Json.Schema 这个库也是基于这个 STJ 编写的。

基准 Schema:

{
  "$id": "http://main",
  "type": "object",

  "additionalProperties": false,
  "patternProperties": {
    "propB*lean": {"type": "boolean"}
  },
  "dependentRequired": {"propNull": [ "propBoolean", "propArray"]
  },
  "dependentSchemas": {
    "propNull": {"type": "object"}
  },
  "propertyNames": true,
  "required": ["propNull", "propBoolean"],
  "maxProperties": 100,
  "minProperties": 0,
  "properties": {
    "propNull": {"type": "null"},
    "propBoolean": {
      "type": "boolean",
      "allOf": [
        true,
        {"type": "boolean"}
      ]
    },
    "propArray": {
      "type": "array",
      "anyOf": [false, true],
      "contains": {"type": "integer"},
      "maxContains": 100,
      "minContains": 2,
      "maxItems": 100,
      "minItems": 1,
      "prefixItems": [{ "type": "integer"}
      ],
      "items": {"type": "integer"},
      "uniqueItems": true
    },
    "propNumber": {
      "type": "number",
      "if": {"const": 1.5},
      "then": true,
      "else": true,
      "enum": [1.5, 0, 1]
    },
    "propString": {
      "type": "string",
      "maxLength": 100,
      "minLength": 0,
      "not": false,
      "pattern": "abcde"
    },
    "propInteger": {
      "$ref": "#/$defs/typeIsInteger",
      "exclusiveMaximum": 100,
      "exclusiveMinimum": 0,
      "maximum": 100,
      "minimum": 0,
      "multipleOf": 0.5,
      "oneOf": [true, false]
    }
  },
  "$defs": {"typeIsInteger": { "$ref": "http://inside#/$defs/typeIsInteger"},
    "toTestAnchor": {"$anchor": "test-anchor"},
    "toTestAnotherResourceRef": {
      "$id": "http://inside",
      "$defs": {"typeIsInteger": { "type": "integer"}
      }
    }
  }
}

有效基准数据:

{
  "propNull": null,
  "propBoolean": true,
  "propArray": [1, 2, 3, 4, 5],
  "propNumber": 1.5,
  "propString": "abcde",
  "propInteger": 1
}

无效基准数据:

{
  "propNull": null,
  "propBoolean": true,
  "propArray": [1, 2, 3, 4, 4], // Two '4', duplicated
  "propNumber": 1.5,
  "propString": "abcde",
  "propInteger": 1
}

基础用法

安装 Nuget package

Install-Package LateApexEarlySpeed.Json.Schema
string jsonSchema = File.ReadAllText("schema.json");
string instance = File.ReadAllText("instance.json");

var jsonValidator = new JsonValidator(jsonSchema);
ValidationResult validationResult = jsonValidator.Validate(instance);

if (validationResult.IsValid)
{
    Console.WriteLine("good");
}
else
{
    Console.WriteLine($"Failed keyword: {validationResult.Keyword}");
    Console.WriteLine($"ResultCode: {validationResult.ResultCode}");
    Console.WriteLine($"Error message: {validationResult.ErrorMessage}");
    Console.WriteLine($"Failed instance location: {validationResult.InstanceLocation}");
    Console.WriteLine($"Failed relative keyword location: {validationResult.RelativeKeywordLocation}");
    Console.WriteLine($"Failed schema resource base uri: {validationResult.SchemaResourceBaseUri}");
}

输出信息

当 json 数据验证失败后,可以查看错误数据的具体信息:

  • IsValid: As summary indicator for passed validation or failed validation.

  • ResultCode: The specific error type when validation failed.

  • ErrorMessage: the specific wording for human readable message

  • Keyword: current keyword when validation failed

  • InstanceLocation: The location of the JSON value within the instance being validated. The value is a JSON Pointer.

  • RelativeKeywordLocation: The relative location of the validating keyword that follows the validation path. The value is a JSON Pointer, and it includes any by-reference applicators such as“

    r

    e

    f

    o

    r

    ref” or ”

    refordynamicRef”. Eg:

    /properties/width/$ref/minimum
    
  • SubSchemaRefFullUri: The absolute, dereferenced location of the validating keyword when validation failed. The value is a full URI using the canonical URI of the relevant schema resource with a JSON Pointer fragment, and it doesn’t include by-reference applicators such as“

    r

    e

    f

    o

    r

    ref” or ”

    refordynamicRef”as non-terminal path components. Eg:

    https://example.com/schemas/common#/$defs/count/minimum
    
  • SchemaResourceBaseUri: The absolute base URI of referenced json schema resource when validation failed. Eg:

    https://example.com/schemas/common
    

性能建议

尽可能的重用已实例化的 JsonValidator 实例(JsonValidator 可以简单理解为代表一个 json schema 验证文档)来验证 json 数据,以便获得更高性能

外部 json schema 依赖的支持

除了自动支持当前 schema 文档内的引用关系,还支持外部 json schema 依赖:

var jsonValidator = new JsonValidator(jsonSchema);
string externalJsonSchema = File.ReadAllText("schema2.json");
jsonValidator.AddExternalDocument(externalJsonSchema);
ValidationResult validationResult = jsonValidator.Validate(instance);
  • 远程 schema url (实现库将访问网络来获得远程的 schema)
var jsonValidator = new JsonValidator(jsonSchema);
await jsonValidator.AddHttpDocumentAsync(new Uri("http://this-is-json-schema-document"));
ValidationResult validationResult = jsonValidator.Validate(instance);

自定义 keyword 的支持

除了 json schema specification 中的标准 keywords 之外,还支持用户创建自定义 keyword 来实现额外的验证需求:

{
  "type": "object",
  "properties": {
    "prop1": {"customKeyword": "Expected value"}
  }
}
ValidationKeywordRegistry.AddKeywordCustomKeyword>();
[Keyword("customKeyword")] 
[JsonConverter(typeof(CustomKeywordJsonConverter))] 
internal class CustomKeyword : KeywordBase
{
    private readonly string _customValue; 

    public CustomKeyword(string customValue)
    {
        _customValue = customValue;
    }

    
    protected override ValidationResult ValidateCore(JsonInstanceElement instance, JsonSchemaOptions options)
    {
        if (instance.ValueKind != JsonValueKind.String)
        {
            return ValidationResult.ValidResult;
        }

        return instance.GetString() == _customValue
            ? ValidationResult.ValidResult
            : ValidationResult.CreateFailedResult(ResultCode.UnexpectedValue, "It is not my expected value.", options.ValidationPathStack, Name, instance.Location);
    }
}
internal class CustomKeywordJsonConverter : JsonConverterCustomKeyword>
{
    
    public override CustomKeyword? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        
        return new CustomKeyword(reader.GetString()!);
    }

    public override void Write(Utf8JsonWriter writer, CustomKeyword value, JsonSerializerOptions options)
    {
        throw new NotImplementedException();
    }
}

Format 支持

目前 library 支持如下 format:

  • uri
  • uri-reference
  • date
  • time
  • date-time
  • email
  • uuid
  • hostname
  • ipv4
  • ipv6
  • json-pointer
  • regex

如果需要自定义 format 验证,可以实现一个 FormatValidator 子类并注册:

[Format("custom_format")] 
public class TestCustomFormatValidator : FormatValidator
{
    public override bool Validate(string content)
    {
        
    }
}


FormatRegistry.AddFormatTypeTestCustomFormatValidator>();

Other extension usage doc is to be continued .

限制

  • 目前 library 关注于验证,暂不支持 annotation
  • 因为暂不支持 annotation, 所以不支持如下 keywords: unevaluatedProperties, unevaluatedItems
  • 目前不支持 content-encoded string

问题报告

欢迎把使用过程中遇到的问题和希望增加的功能发到 github repo issue 中

More doc is to be written

原文地址: Json Schema 简介和 Json Schema 的高性能.net 实现库 LateApexEarlySpeed.Json.Schema

    正文完
     0
    Yojack
    版权声明:本篇文章由 Yojack 于2024-10-07发表,共计8517字。
    转载说明:
    1 本网站名称:优杰开发笔记
    2 本站永久网址:https://yojack.cn
    3 本网站的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,请联系站长进行删除处理。
    4 本站一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
    5 本站所有内容均可转载及分享, 但请注明出处
    6 我们始终尊重原创作者的版权,所有文章在发布时,均尽可能注明出处与作者。
    7 站长邮箱:laylwenl@gmail.com
    评论(没有评论)