使用 JsonSchema 验证 API 的返回格式

Posted dotNET跨平台

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用 JsonSchema 验证 API 的返回格式相关的知识,希望对你有一定的参考价值。

使用 JsonSchema 验证 API 的返回格式

Intro

最近我们的 API 提供给了别的团队的小伙伴用,按照他们的需求做了接口的改动,API 返回的数据结构有一些变化,我们提供的接口有缓存,数据库更新之后不会马上刷新,于是就想验证一下数据是不是已经更新成最新的版本,都更新好了之后就告诉别的团队的小伙伴做集成,那么如何来验证是不是最新版本 API 呢?现在我们的 API 基本上都是 JSON,于是就想尝试使用 Json Schema 来验证我们的 API

What

Json 是 JavaScript Object Notation 的缩写,它是一种简化的数据交换格式,是目前互联网服务间进行数据交换最常见的一种交换格式,具有简洁、可读性好等特点。

Json Schema 是用来定义 json 数据约束的一个标准,可以清晰的描述JSON数据的结构,是一种描述JSON数据的JSON数据。。根据这个约定模式,交换数据的双方可以理解 json 数据的要求和约束,也可以据此对数据进行验证,保证数据交换的正确性。

Why

为什么我们要使用 JSON schema 呢,在我们日常的使用中你也可以已经在使用了,只是没有察觉,我们在编辑一些 JSON 配置文件的时候,有时候会有一些提示,自动提示匹配的属性名,这其实就是 Json shcema 带来的好处,一些比较知名的项目的配置文件大多会提供一个自己的 json schema,这不仅仅会为开发者们带来配置的便利,也会便于实现 json 格式的校验

总结一下 Json Schema 能够带来的好处

  • 智能提示 json-schema-lint

  • 格式校验,自动化测试

  • Mock 数据生成,基于 schema 的数据生成

支持 Json-Schema 的编辑器有很多

  • android Studio

  • CLion

  • IntelliJ IDEA

  • JSONBuddy

  • Neovim

  • phpStorm

  • PyCharm

  • ReSharper

  • Rider

  • RubyMine

  • Visual Studio 2013+

  • Visual Studio Code

  • Visual Studio for Mac

  • WebStorm

  • ...

Sample

对于下面这样一段 json


  "productId": 1,
  "productName": "A green door",
  "price": 12.50,
  "tags": [ "home", "green" ]

JsonSchema 示例:


  "$schema": "https://json-schema.org/draft/2020-12/schema", // schema 规范版本,可以没有
  "$id": "https://example.com/product.schema.json", // json schema 地址,可以没有
  "title": "Product", // json schema 的标题信息,可以没有
  "description": "A product from Acme's catalog", // json schema 的描述信息,可以没有
  "type": "object", // 数据类型
  "properties":  // 对象属性信息
    "productId":  //属性名称
      "description": "The unique identifier for a product", // 属性描述
      "type": "integer" // 属性类型
    ,
    "productName":  //属性名称
      "description": "Name of the product", // 属性描述
      "type": "string" // 属性类型
    ,
    "price":  //属性名称
      "description": "The price of the product", // 属性描述
      "type": "number", // 属性类型
      "exclusiveMinimum": 0 // 约束最小值不能小于0
    ,
    "tags":  // 属性名称
      "description": "Tags for the product", // 属性描述
      "type": "array", // 属性类型
      "items": 
        "type": "string" // 属性类型
      ,
      "minItems": 1, // 约束条件,至少要有一个元素
      "uniqueItems": true // 不能有重复项
    
  ,
  "required": [ "productId", "productName", "price" ] // 必须的属性,不存在则不符合 schema 的约束

JSON Schema 的核心定义了以下基本类型:

  • string

  • number

  • integer

  • object

  • array

  • 布尔值

  • null

除了上面的这些验证类型,还有很多验证,具体可以参考:https://json-schema.org/draft/2020-12/json-schema-validation.html

Pracetice

我选择的是 JsonSchema.Net,这个是基于 System.Text.Json 来实现的一个 Json schema 的扩展,使用起来也还好,上手也比较简单

构建 json schema 的简单示例:

var jsonSchema = new JsonSchemaBuilder()
    .Properties(
        ("name", new JsonSchemaBuilder()
            .Type(SchemaValueType.String)
            .MinLength(1)
            .MaxLength(10)
        ),
        ("age", new JsonSchemaBuilder()
            .Type(SchemaValueType.Number)
            .Minimum(1)
        )
        )
    .Required("name")
    .Build();

这个示例构建了一个简单的 json 对象,这个对象有两个属性一个 name 一个 age,其中 name 是必须的属性,

name 是一个最小长度为1,最大长度为 10 的字符串,age 是一个最小值为 1 的数字

除了使用 JsonSchemaBuilder 自己构建一个 json schema,现在有很多在线的基于一段 json 自动生成 json schema 的工具,我们也可以从一个 json schema 文件或者一个 Stream或者一段 schema 文本来获取一个 json schema,本质就是读取一段 json 反序列成了一个 json schema 对象

const string testJsonSchema = @"

  ""$schema"": ""https://json-schema.org/draft/2020-12/schema"",
  ""type"": ""object"",
  ""properties"": 
    ""Data"": 
      ""type"": ""array"",
      ""items"":
        
          ""type"": ""object"",
          ""properties"": 
            ""NoticeTitle"": 
              ""type"": ""string""
            ,
            ""NoticeCustomPath"": 
              ""type"": ""string""
            ,
            ""NoticePublishTime"": 
              ""type"": ""string""
            
          ,
          ""required"": [
            ""NoticeTitle"",
            ""NoticeCustomPath"",
            ""NoticePublishTime""
          ]
        
    ,
    ""PageNumber"": 
      ""type"": ""integer""
    ,
    ""PageSize"": 
      ""type"": ""integer""
    ,
    ""TotalCount"": 
      ""type"": ""integer""
    ,
    ""PageCount"": 
      ""type"": ""integer""
    ,
    ""Count"": 
      ""type"": ""integer""
    
  ,
  ""required"": [
    ""Data"",
    ""PageNumber"",
    ""PageSize"",
    ""TotalCount"",
    ""PageCount"",
    ""Count""
  ]

";
var schema = JsonSchema.FromText(testJsonSchema);

有了 json schema 之后我们就可以用来验证 json 是否合法了,JsonSchema 中有一个 ValidationResults Validate(JsonElement root, ValidationOptions? options = null) 的方法

在 2.2.0 之前的版本你需要将 json 转换为 JsonElement 来进行验证,下面是文档给出的示例,你需要先获取获取一个 JsonDocument,然后使用 JsonDocumentRootElement 来验证

JsonSchema schema = new JsonSchemaBuilder()
    .Properties(
        (
            "myProperty", new JsonSchemaBuilder()
                .Type(SchemaValueType.String)
                .MinLength(10)
        )
    )
    .Required("myProperty");
var emptyJson = JsonDocument.Parse("").RootElement;
var booleanJson = JsonDocument.Parse("\\"myProperty\\":false").RootElement;
var stringJson = JsonDocument.Parse("\\"myProperty\\":\\"some string\\"").RootElement;
var shortJson = JsonDocument.Parse("\\"myProperty\\":\\"short\\"").RootElement;
var numberJson = JsonDocument.Parse("\\"otherProperty\\":35.4").RootElement;
var nonObject = JsonDocument.Parse("\\"not an object\\"").RootElement;

var emptyResults = schema.Validate(emptyJson);
var booleanResults = schema.Validate(booleanJson);
var stringResults = schema.Validate(stringJson);
var shortResults = schema.Validate(shortJson);
var numberResults = schema.Validate(numberJson);
var nonObjectResults = schema.Validate(nonObject);

感觉这样太不方便,于是就写了两个扩展方法来方便直接从 JsonDocumentstring 来验证

public static ValidationResults Validate(this JsonSchema jsonSchema, JsonDocument jsonDocument, ValidationOptions? validationOptions = null)

    return jsonSchema.Validate(jsonDocument.RootElement, validationOptions);


public static ValidationResults Validate(this JsonSchema jsonSchema, string jsonString, ValidationOptions? validationOptions = null)

    using var jsonDocument = JsonDocument.Parse(jsonString);
    return jsonSchema.Validate(jsonDocument, validationOptions);

并且提了 PR,现在使用 2.2.0 版本就可以直接用了

var validateResults = schema.Validate("");
WriteLine(validateResults.IsValid);

返回的结果中 IsValid 就代表了这个 Json 是否符合这个 json schema 的约束,true 就是满足约束,false 就是不满足

默认的验证结果,不会返回具体的错误信息,你可以指定一个 ValidationOption,指定 OutputFormatDetailed 来返回具体的错误信息

var schema = JsonSchema.FromText(testJsonSchema);

var validationOptions = new ValidationOptions()

    OutputFormat = OutputFormat.Detailed
;

var invalidJson = @"
  ""Data"": [
    
      ""NoticeExternalLink"": null
    
  ],
  ""PageNumber"": 1,
  ""PageSize"": 10,
  ""TotalCount"": 5,
  ""PageCount"": 1,
  ""Count"": 5

";

var validateResult = schema.Validate(invalidJson, validationOptions);
WriteLine(validateResult.IsValid);
WriteLine(validateResult.Message);

输出结果如下:

False
Required properties [NoticeTitle, NoticeCustomPath, NoticePublishTime] were not present

验证 API 返回结果:

using var httpClient = new HttpClient();
var result = await httpClient.GetStringAsync("http://reservation.weihanli.xyz/api/notice");
validateResult = schema.Validate(result, validationOptions);
WriteLine(validateResult.IsValid);
WriteLine(validateResult.Message);

More

这个库的作者除了实现了 JsonSchema 的支持,还提供了对于 JsonPath、JsonPatch 等支持,有需要的可以关注一下 https://github.com/gregsdennis/json-everything

作者还提供了一个扩展库,可以基于强类型的 Model 直接生成一个 schema,不需要再自己构建 schema

除了这个库你也可以选择别的库来实现,Newtonsoft.Json.Schema 是基于 Newtosoft.Json 实现的 JsonSchema 的支持,也可以尝试一下

References

  • https://json-schema.org/

  • https://www.tutorialspoint.com/json/json_schema.htm

  • https://json-schema.org/learn/getting-started-step-by-step.html

  • https://json-schema.org/draft/2020-12/json-schema-validation.html

  • https://json-schema.apifox.cn/

  • https://www.jsonschemavalidator.net/

  • https://www.schemastore.org/json/

  • https://github.com/gregsdennis/json-everything

  • https://github.com/gregsdennis/json-everything/pull/238

  • https://github.com/WeihanLi/SamplesInPractice/blob/master/JsonSample/SystemTextJsonSample/JsonSchemaSample.cs

以上是关于使用 JsonSchema 验证 API 的返回格式的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 JsonSchema 进行实时客户端验证?

对于具有递归$ ref的JSONSchema,ajv验证失败

.Net使用JsonSchema验证Json

Python jsonschema 验证时区

通过JSONSchema完成接口自动化测试的数据验证

自动化脚本实现Json Schema验证