• Json Schema


    经常用 Python 写代码的同学应该都有一个感触,那就是 Python 对于字典的支持太舒服了,而且基本上可以和 JS 中写 Json 一样舒服。但是,因为 Python 对于 Dict 的支持比较松散,所以,导致了一个问题,假如我有一个函数,参数如果放它一个字典,那调用者会疯掉的,这是一种情况;另外一个常见的场景就是参数校验,无论是 HTTP 还是 RPC 等形式,很多时候我们的参数都是以 JSON 的形式传递的,如何对这结构体进行描述或者验证都是一个比较棘手的问题。

    对于 JSON 的这个问题,我关注它也有两年多的时间了,我觉得还没见到一种完美的方式,总是带着一些遗憾,但是不无办法,例如本文就介绍一种还不错的方式,但是,缺点就是语法其实还是比较复杂的,制定 Schema 的时候有阵痛,但是用起来的时候就很方便、很舒服了,这就是我在这篇文章中想介绍的一个 Python Library:JSON Schema

    json schema 简介

    关于 json schema 其实无需介绍过多,无非就是一个定义 json 的 schema 的 library,其中肯定包含两部分,第一部分是 Schama 的定义,这个属于文档方面的事情;另一部分就是 Schema 的使用,这是属于开发方面的问题。本文就从这两方面说起,看看 JSON-Schema 是如何处理 json 的 schema 的。

    在讲关于这个 python library 之前,顺便聊一些题外话,那就是 json 的 schema 问题,在使用 json-schema 这个 Library 的时候,我被导到一个 json-schema.org 的网站上,然后这里定义了关于 json schema 的相关的规范,最新的版本是 draft-7,从这个版本来看,似乎这还不是正式版本,不过既然有规范,而且我针对我的需求,扫了几圈这个规范,发现是挺完善的,所以,不妨将这个规范用作日常使用。

    json schema 定义

    json-schema 这个 library 在它的描述中声明目前是完全支持:Draft3Draft4 规范的,所以我这里就以 Draft4 为规范进行 json 的 schema 描述。

    先说一下在本文中我将使用的示例背景,在本文中,我将描述一个博客文章的模型,然后这个博客文章会属于一些分类分类是嵌套在文章中的,这里其实分类文章是一个多对多的关系,但是因为分类自身比较简单,所以为了简化模型,我将分类嵌入到文章模型中,所以整体模型是这样的:

    依照这个模型,我可以先定义一个简单的对应的 json 序列:

    接着我们就可以先使用 json-schema 来定义一下我们有哪些 Field,注意,这里我们先不做其他方面的定义进行定义,而只是先看看需要什么 Field,那么一个非常简单的 Schema 就出来了,像这样:

    各要素都比较完备了,简单明了的说明了我们的 post 的数据结构,下一步就是定义一下各个字段的类型了。

    json schema 类型

    在 json schema 中,描述类型都是通过 K-V 的形式进行描述的,并且 Key 肯定是 "type",然后 value 就是对应的类型,这个对应的类型不是随便写的,只能从下面这 6 个中选择:

    • "null"
    • "boolean"
    • "object"
    • "array"
    • "number"
    • "string"

    所以我们就可以先填充一下我们的 Schema:

    ok,现在看上去这个 Schema 已经有点成效了,但是,对于一些字段我们可能还需要进行更多的限制,例如 status,我们不能让别人随意的发挥,所以自然是需要一些 ENUM 值进行选择,所以也需要制定以下。

    json schema 属性

    在 json schema 中,字段除了类型,还可以添加一些属性,常用的属性有

    • enum:列举一些枚举值,表示该字段只能是枚举值中的其中一个,枚举值必须为数值
    • maxLength/minLength:字符串的长度限制
    • pattern:字符串必须满足的模式

    这里就给我们的 schema 加上一些限制:

    似乎更好一些了,然后我们看向了 categories,觉得这个只指定了类型为 array,但是 array 里面的元素应该是什么类型却没有说,所以我们还是需要指定一下的。

    json schema 数组

    在 json-schema 中,如果要定义数组的元素类型,那么可以通过 items 属性定义,定义的方式就和定义一个对象一致,因此,我们就可以这么搞了:

    这样一定义,我们的数组 schema 就完整多了,但是,从这里我们就可以发现,这里的 分类 Model 还比较简单,如果更复杂一些,那么 Post 的 Schema 就比较难看了,有没有一些更好的方式可以简化这个 schema。

    json schema 引用

    在 json-schema 中,我们经常会有一个 Object 中包含另外一个 Object 的情况,对于这种情况,当然可以在 Object 定义其他 Object,但是,正如前面所说,这样太复杂了,所以,为了更加简单清晰得描述关系,在 json-schema 中支持 reference,使用方式为在 Object 中使用关键字 $ref,然后值为对应 Object 的定义,需要注意的是,这个 Object 的定义需要在同个 Object 的 definitions 的 value 中,示例为:

    这样,对于 Post 这个 Schema 就清晰多了。

    这些就是 json-schema 一些常见的用法,通过这些用法的组合,足以让我们应对大部分的场景。如果当你遇到应对不了的场景的时候,别慌,可以去看看 schema 规范:json-schema,这份规范不复杂,可以较快得找到你需要的内容。

    json schema 校验

    当我们把 schema 定义好了之后,是时候尝试在代码中使用一把了,我这里使用的还是 Python 编程语言,这里我不能说使用任何编程语言都可以,根据我查找过程中的一些观察,似乎除了 Python,还有 Java 我是明确知道支持的,至于其他语言我就不太清楚了。

    在 Python 中,首先第一步我们肯定是离不开安装依赖的 Library 的,肯定是老方法,直接用 pip 安装了,有简单的方式为什么不用呢:

    $ pip install jsonschema
    

    然后使用过程也是极其简单,下面这是一个针对我们刚才的 schema 的验证代码,可以一直执行尝试一下:

    然后执行一遍看看,结果肯定是:

    $ json is ok!
    

    假设我们修改一下 json,看看不 ok 的时候又是什么情况?如果你试了,你肯定会发现不会输出 "json is invalid!" 这个错误,反而是直接抛出了异常,但是异常的内容又很多,非常得详细,有时我们不需要知道这么多,所以为了简略处理,我们需要对 validate 这个函数有所了解,这里对函数原型做一个简单的观看:

    这里讲了可能抛出两种异常,分别是 :

    • ValidationError
    • SchemaError

    所以我们在做开发调试的时候需要注意一些,如果是我们的 Schema 都编写错了,那么无论验证什么 json 必然都是会抛出异常的,这个坑是值得注意的。然后我们再看下 ValidationError 这个异常有啥内容:

    可以发现其实这两种异常都是相同的属性,所以我们将他们的内容都打印出来看看:

    message: 'hello' is not one of ['PostStatus.PUBLISHED', 'PostStatus.DELETED', 'PostStatus.DRAFT']
    path: deque(['status'])
    cause: None
    context: []
    instance: hello
    schema: {'enum': ['PostStatus.PUBLISHED', 'PostStatus.DELETED', 'PostStatus.DRAFT'], 'type': 'string'}
    schema_path: deque(['properties', 'status', 'enum'])
    parent: None
    

    ok,这样我们就很清楚每个字段表示的含义了,同时就可以在开发的时候根据自己的需要使用对应的字段了,一般情况下我建议直接使用 message 就可以了,所以最后示例代码修改成了:

    总结

    在本文中,我尝试以一种比较简单的方式对 json 的 schema 定义进行了一个介绍,同时,也一步步得对一个简单的示例进行扩展,最后使用 Python 编程语言对这个 Schema 进行验证。json 的使用在日常的开发中极其常见,而且因为其不错的灵活性,给我们开发带来了很大的方便;但是,在享受方便的同时,也经常给我们带来的意想不到的 BUG,所以,对于一些比较重要的出入口,不妨加上一个 Schema 校验,让程序运行得更健壮些。

  • 相关阅读:
    LeetCode–打印从1到最大的n位数
    常用十大算法(十)— 踏棋盘算法
    常用十大算法(九)— 弗洛伊德算法
    常用十大算法(八)— 迪杰斯特拉算法
    LeetCode–组合
    LeetCode–组合总和
    5513. 连接所有点的最小费用 kruskal
    152. 乘积最大子数组 dp
    1567. 乘积为正数的最长子数组长度 dp
    5481. 得到目标数组的最少函数调用次数. 位运算
  • 原文地址:https://www.cnblogs.com/makor/p/Json-Schema.html
Copyright © 2020-2023  润新知