使用 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,然后使用 JsonDocument 的 RootElement 來驗證
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);感覺這樣太不方便,于是就寫了兩個擴展方法來方便直接從 JsonDocument 和 string 來驗證
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,指定 OutputFormat 為 Detailed 來返回具體的錯誤信息
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 的返回格式的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: WPF 实现截屏控件之移动(二)(仿微信
 - 下一篇: 甲骨文严查Java授权,企业连夜删除JD