使用 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/289919.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

盘点PHP编程常见失误

为什么80%的码农都做不了架构师?>>> 变量声明 如果在一条语句中声明一个变量,如下所示:$varvalue;编译器首先会求出语句右半部分的值,恰恰正是语句的这一部分常常会引发错误。如果使用的语法不正确,就会出…

Scala具体解释---------Scala是什么?可伸展的语言!

Scala是什么 Scala语言的名称来自于“可伸展的语言”。之所以这样命名,是由于他被设计成随着使用者的需求而成长。你能够把Scala应用在非常大范围的编程任务上。从写个小脚本到建立个大系统。51CTO编辑推荐:Scala编程语言专题 Scala是非常easy进入的语言…

地理学:从未磨灭的价值

地理学:从未磨灭的价值 文|梁鹏 庄子说:“天地有大美而不言,四时有明法而不议,万物有成理而不说”,天地不言,四时不议,万物不说,于是地理学家就是替天地说话的那帮人。推开自然之门&…

Android之导入项目提示Android requires compiler compliance level 5.0 or 6.0. Found ‘1.8‘ instead解决办法

1、问题 导入项目eclipse提示如下: Android requires compiler compliance level 5.0 or 6.0. Found 1.8 instead 2、解决办法 项目右键->Android tools->Fix Project

Educational Codeforces Round 1

被C坑的不行不行的。。。其他题目都还可以。 A - Tricky Sum 求1,2,3,...,n的加和,其中2^x(x>0)为负。 因为2^x的个数很少,所以以每个2^x为分界点进行判断. 初始化x0; 如果n>2^x,求出2^(x-1)到2^(x)之…

自定义View的三个构造函数

自定义View有三个构造方法,它们的作用是不同的。 public MyView(Context context) {super(context); }public MyView(Context context, AttributeSet attrs) {super(context, attrs);}public MyView(Context context, AttributeSet attrs, int defStyleAttr) {su…

mysql查询今天_昨天_7天_近30天_本月_上一月 数据_mysql查询今天、昨天、7天、近30天、本月、上一月 数据...

获取当前时间CURTIME();查询今天的数据select * from table where to_days(time) to_days(now())查询昨天数据select * from error where to_days(now())-to_days(alarmtime)1查询最近一个星期数据select * from error where to_days(now())-to_days(alarmtime)<7SELECT * …

甲骨文严查Java授权,企业连夜删除JDK

文 | Travis出品 | OSC开源社区&#xff08;ID&#xff1a;oschina2013)根据外媒 The Register 报道和各大企业的反馈&#xff0c;甲骨文公司近日已经开始将 Java 纳入其软件许可审查中&#xff0c;目的是找出那些处于不合规边缘或已经违规的客户&#xff0c;甲骨文此举是为了推…

前端日志分析

前端日志分析介绍 前端日志分析是通过搜集访客访问网站的行为数据&#xff0c;然后在这些用户日志数据的基础上通过定量和定性分析&#xff0c;来改善用户的浏览体验及网站性能&#xff0c;最终提升商业回报的过程&#xff0c;通常&#xff0c;前端日志分析遵循以下步骤…

历史之外,地理之中

历史之外&#xff0c;地理之中 ◎梁鹏 沉迷于历史&#xff0c;却最终选择了地理&#xff0c;于是苦苦寻求历史与地理之间的共通衔接之处。于学科而言&#xff0c;是历史与地理&#xff0c;于我而言&#xff0c;是梦想与现实。我知道已经存在的历史地理学不是我的归宿&#xff…

linux之readelf命令

1、readelf命令解释 readelf命令用来显示一个或者多个elf格式的目标文件的信息 2、ELF文件类型 可重定位文件:用户和其他目标文件一起创建可执行文件或者共享目标文件,例如lib*.a文件。 可执行文件:用于生成进程映像,载入内存执行,例如编译好的可执行文件a.out。 共享目标文件…

zuul转发的一些常见异常

为什么80%的码农都做不了架构师&#xff1f;>>> ##序 使用zuul作为api网关的话&#xff0c;经常会碰见一些异常&#xff0c;这里小结一下。 ##ZuulException 这个是最外层的异常 public class ZuulException extends Exception {public int nStatusCode;public Str…

Java中如何利用gson解析数据

最近在学习Java&#xff0c;需要用到json&#xff0c;本人对java不是特别精通&#xff0c;于是开始搜索一些java平台的json类库。 发现了google的gson&#xff0c;带着一些好奇心&#xff0c;我开始使用了gson。 经过比较&#xff0c;gson和其他现有java json类库最大的不同时g…

python基础入门大作业怎么做_【百度飞桨】零基础Python课程大作业

转眼间百度飞桨的零基础python课程马上就要结束了&#xff0c;所谓年前学python&#xff0c;年后来上号&#xff0c;通过本次课程可以对python有一个基础的认知和掌握&#xff0c;以下是大作业的个人代码&#xff0c;仅供参考。【作业一】#创建Student类class Student:def __in…

Xamarin效果第八篇之视频监控

还记得全年帮助一个朋友通过技术手段写了一个PC端的监控软件,这不再次想起此事,准备基于Xamarin再来实现一个移动端的监控;毕竟直接手机上打开还是比较方便的;最终实现的效果:1、启动页动画,原来直接贴图片;这次尝试使用Lottie来玩玩,直接贴参考连接https://www.codesitory.com…

Python 日期格式相关

今天看网上一个说中文日期的问题. 自己试了下.#-*- coding: gb2312 -*- import datetime, time#now time.strftime(%Y年%m月%d日 %H时%M分%S秒, time.localtime()).decode(utf-8) now time.strftime(%Y年%m月%d日 %H时%M分%S秒, time.localtime()) print nownow time.strpti…

Android之解决java.lang.UnsatisfiedLinkError: dlopen failed: ××××.so: has text relocations

1、问题 项目导入别人的so,有个项目没问题,但是这个有问题,运行提示这个错误 java.lang.UnsatisfiedLinkError: dlopen failed: .so: has text relocations 2、临时规避的解决办法 把现在的项目targetSdkVersion版本降低: such as,又24变为22,这样可以解决问题(没有源代…

前端实现连连看小游戏(1)

博主玩了这么久的连连看&#xff0c;居然是第一次发现&#xff0c;连连看最多只能有2个转弯。orz… 在网上搜索连连看的连线算法判断&#xff0c;并没有找到很全面的&#xff0c;经过自己摸索之后&#xff0c;做了一些小动画&#xff0c;希望大家可以看一遍都懂啦&#xff5e;&…

在 Visual Studio 2010 中创建 ASP.Net Web Service

第一步&#xff1a;创建一个“ASP.Net Empty Web Application”项目 第二步&#xff1a;在项目中添加“Web Service”新项目 第一步之后&#xff0c;Visual Studio 2010会创建一个仅含一个站点配制文件&#xff08;Web.config&#xff09;的空站点&#xff0c;其余的什么也没有…

C#中缓存的使用

简介缓存是指可以进行高速数据交换的存储器&#xff0c;它先于内存与CPU交换数据&#xff0c;因此速率很快。由于CPU从内存中读取数据的速度比从磁盘读取快几个数量级&#xff0c;并且存在内存中&#xff0c;减小了数据库访问的压力&#xff0c; 所以缓存几乎每个项目都会用到。…