Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JSON 与 JSON Schema:从结构到规范的转换之旅 #281

Open
yanyue404 opened this issue Sep 24, 2024 · 0 comments
Open

JSON 与 JSON Schema:从结构到规范的转换之旅 #281

yanyue404 opened this issue Sep 24, 2024 · 0 comments

Comments

@yanyue404
Copy link
Owner

yanyue404 commented Sep 24, 2024

什么是 JSON Schema?

JSON Schema 是对 JSON 数据格式的描述和规范,提供了一种方式来约束和明确数据的类型和结构。可以把 JSON Schema 看作是 JSON 格式的一种“类型系统”,它就像 TypeScript 对 JavaScript 的作用一样。

数据类型

在 JSON Schema 中使用最多的是type关键字,它包含了 JSON 格式的基本类型

类型 描述
string 字符串型,双引号包裹的 Unicode 字符和反斜杠转义字符
number 数字型,包括整型(int)和浮点数型(float)
boolean 布尔型,true 或 false
object 对象型,无序的键:值对集合
array 数组型,有序的值序列
null 空型

关键字

下面以 Object 类型为例,列举在 JSON Schema 中经常使用到的关键字和作用。示例 JSON Schema 如下:

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "$id": "https://example.com/schemas/person",
  "title": "base info",
  "description": "base information about person",
  "type": "object",
  "required": ["name", "age", "phone"],
  "definitions": {
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 10
    }
  },
  "properties": {
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 10
    },
    "age": {
      "type": "number",
      "minimum": 18,
      "exclusiveMinimum": true,
      "maximum": 65,
      "exclusiveMaximum": true
    },
    "phone": {
      "type": "string",
      "pattern": "^1\\d{10}$"
    },
    "parents": {
      "type": "array",
      "items": [{ "$ref": "#/definitions/name" }],
      "minItems": 1,
      "maxItems": 2,
      "uniqueItems": true
    },
    "address": {
      "type": "object",
      "properties": {
        "city": {
          "type": "string",
          "enum": ["guangzhou", "beijing"]
        }
      }
    }
  }
}

这个示例 JSON Schema 描述了一个人的基本信息对象,包含了多个字段和相应的验证规则。

关键字 描述
$schema 声明此 json 片段属于 JSON Schema,并遵循所声明的 JSON Schema 版本规范
$id 为 JSON Schema 声明一个统一资源标识符,使解析$ref 时能够引用片段
title 为 JSON Schema 文件提供标题
description 为 JSON Schema 文件提供描述信息
definitions 声明子 schema,使解析$ref 能够引用片段
$ref 引用 JSON Schema 片段
required 定义对象类型 properties 所声明的字段是否必须,值必须是数组,数组中的元素必须是字符串类型且唯一
type 定义元素的类型
properties 定义对象类型里的属性(键值对),每个字段的值都是一个有效的 schema 片段,用来限制每个字段的格式
minimum 约束取值范围,标识取值范围应该大于或等于 minimum
exclusiveMinimum 假若 minimum 或 exclusiveMinimum 同时存在,且 exclusiveMinimum 为 true,则取值范围大于 minimum
maximum 约束取值范围,标识取值范围应该小于或等于 maximum
exclusiveMaximum 假若 maximum 或 exclusiveMaximum 同时存在,且 exclusiveMaximum 为 true,则取值范围小于 maximum
minLength 字符串类型数据的最小长度
maxLength 字符串类型数据的最大长度
pattern 使用正则表达式约束字符串类型数据
items 用来定义数组类型的子元素,值必须为数组,且是一个有效的 schema 片段
minItems 定义数组类型大小的最小长度
maxItems 定义数组类型大小的最大长度
uniqueItems 定义数组类型子元素是否必须唯一
enum 用来限制值的范围,值必须在 enum 所指定的集合里面

以上列举的是一些常用到的关键字。要深入了解和使用更多关键字,可以参考 JSON Schema 官方文档

JSON Schema 的应用场景

JSON Schema 的优点在于可以对数据类型进行描述,方便理解。同时也让机器“读懂”,比如数据校验或、输入检测提示、自动化测试等等。

  1. 数据校验

基于 JSON Schema 提供多种校验约束条件,可以定义数据的校验规则,通过 JSON Schema 进行数据校验,多用于接口请求参数校验,表单校验,和数据校验自动化测试上

验证器

JSON Schema 校验工具:Ajv

// or ESM/TypeScript import
import Ajv from 'ajv'
// Node.js require:
const Ajv = require('ajv')

const ajv = new Ajv() // options can be passed, e.g. {allErrors: true}

const schema = {
  type: 'object',
  properties: {
    foo: { type: 'integer' },
    bar: { type: 'string' }
  },
  required: ['foo'],
  additionalProperties: false
}

const data = {
  foo: 1,
  bar: 'abc'
}

// 最佳性能:compile 和 getSchema 方法,使用由 compile 或 getSchema 方法返回的编译函数时可以获得最佳性能。
const validate = ajv.compile(schema)
// 每次调用验证函数(或 ajv.validate )时, errors属性都会被覆盖
const valid = validate(data)
if (!valid) console.log(validate.errors)
  1. 提供更为准确可靠的 mock 数据

基于 JSON Schema 提供多种校验约束条件,可以使用它原生的能力来生成更为准确可靠的 mock 数据

使用 JSON Schema 生成 mock 数据在线工具:

  1. 基于 JSON Schema 配置文件渲染 UI 组件

例如: https://github.com/lljj-x/vue-json-schema-form/tree/master, 其基于 Vue/Vue3,Json Schema 和 ElementUi/antd/iview3/naiveUi 等生成 HTML Form 表单,可用于活动编辑器、h5 编辑器、cms 等数据配置;支持可视化生成表单 Schema 。

Schema

{
  "title": "测试注册表单",
  "description": "A simple form example.",
  "type": "object",
  "required": ["firstName", "lastName"],
  "ui:order": ["lastName", "firstName", "*", "password"],
  "properties": {
    "firstName": {
      "type": "string",
      "title": "First name",
      "default": "Jun"
    },
    "lastName": {
      "type": "string",
      "title": "Last name",
      "ui:options": {
        "description": "请输入你的姓"
      },
      "err:required": "必须输入Last Name"
    },
    "price": {
      "type": "string",
      "description": "最多输入两位小数点,最大值 999999.99",
      "title": "价格",
      "format": "price"
    },
    "age": {
      "type": "integer",
      "title": "Age",
      "maximum": 80,
      "minimum": 16
    },
    "bio": {
      "type": "string",
      "title": "Bio",
      "minLength": 10
    },
    "password": {
      "type": "string",
      "title": "Password",
      "minLength": 3
    },
    "telephone": {
      "type": "string",
      "title": "Telephone",
      "minLength": 10
    }
  }
}

FormData

{
  "firstName": "Jun",
  "lastName": "Liu",
  "age": 80,
  "bio": "知道的越多、就知道的越少",
  "password": "My.Pass",
  "telephone": "1881446xxxx"
}
  1. 低代码 low-code
  • 整个页面规范 JSON Schema
  • 组件配置页按 JSON Schema 驱动
  • 视图页消费解析 JSON Schema 并渲染视图

低代码组件配置示例

1. 对象

(1)方案 A

const props = {
  // data 组件内置
  scrollArea: {
    type: Object,
    desc: '滚动条区域',
    def: () => {
      return {
        scrollType: {
          type: String,
          desc: '滚动条展示文案类型'
        },
        scrollTypeSwitch: {
          type: Boolean,
          desc: '展示开关'
        }
      }
    }
  },
  btnStyle: {
    type: Object,
    desc: '按钮自定义样式',
    def: () => {}
  },
  btnWrapStyle: {
    type: Object,
    desc: '按钮区域自定义样式',
    def: () => {}
  },
  clickArea: {
    type: 'CLICK_AREA',
    desc: '配置图片可点击区域'
  }
}

(2)方案 B

const props = {
  data: {
    type: Object,
    desc: '数据源配置',
    default: () => {
      return {
        headImageUrl: {
          type: String,
          desc: '头图链接',
          component: 'Upload',
          default:
            'https://ecuat.tk.cn/tkcms/file/upload/mob/productImg/zwt1.png'
        },
        saleImageUrl: {
          type: String,
          desc: '卖点图链接',
          component: 'Upload',
          default:
            'https://ecuat.tk.cn/tkcms/file/upload/mob/productImg/zwt2.png'
        }
      }
    }
  },
  scrollArea: {
    type: Object,
    desc: '滚动条区域',
    default: () => {
      return {
        scrollType: {
          type: String,
          desc: '滚动条展示文案类型',
          default: 'normal',
          component: 'Select',
          selectList: [
            {
              key: 'normal',
              value: '默认'
            },
            {
              key: 'womon',
              value: '女性'
            },
            {
              key: 'child',
              value: '少儿'
            },
            {
              key: 'pet',
              value: '宠物'
            }
          ]
        },
        scrollTypeSwitch: {
          type: Boolean,
          desc: '展示开关',
          default: true
        }
      }
    }
  },
  btnStyle: {
    type: Array,
    component: 'Css',
    desc: '投保按钮自定义样式',
    default: () => [
      {
        background: 'linear-gradient(180deg,#63c3ff, #3f80ff 76%)'
      },
      {
        boxShadow:
          '0px 1px 4px 0px rgba(87,163,253,0.50), inset 1px 0px 6px 0px rgba(132,186,255,0.40)'
      }
    ]
  },
  btnWrapStyle: {
    type: Object,
    desc: '按钮区域自定义样式',
    default: () => {
      return {
        top: {
          type: String,
          desc: '按钮距离顶部的距离',
          default: '2.76rem'
        }
      }
    }
  }
}

(3) 方案 C

{
  "title": "组件的配置",
  "description": "组件的描述",
  "type": "object",
  "properties": {
    "data": {
      "title": "数据源配置",
      "type": "object",
      "properties": {
        "headImageUrl": {
          "type": "string",
          "description": "头图链接"
        },
        "saleImageUrl": {
          "type": "string",
          "description": "卖点图"
        }
      },
      "required": ["headImageUrl", "saleImageUrl"]
    },
    "scrollArea": {
      "title": "滚动条区域",
      "type": "object",
      "properties": {
        "scrollType": {
          "type": "string",
          "description": "滚动条展示文案类型",
          "oneOf": [
            { "const": "normal", "title": "普通" },
            { "const": "womon", "title": "女性" },
            { "const": "child", "title": "少儿" },
            { "const": "pet", "title": "宠物" }
          ]
        },
        "scrollTypeSwitch": {
          "description": "展示开关",
          "type": "boolean"
        }
      }
    },
    "btnStyle": {
      "title": "按钮样式",
      "type": "array",
      "items": {
        "type": "object"
      },
      "minItems": 0,
      "uniqueItems": true
    },
    "btnWrapStyle": {
      "title": "按钮外部样式",
      "type": "object",
      "properties": {
        "top": {
          "type": "integer",
          "description": "按钮距离顶部的距离",
          "minimum": 0,
          "maximum": 10
        }
      }
    }
  }
}

默认值采用,新增 default 字段

定制组件采用,新增 useComponent 字段 b

2. 数组

(1) 方案 A

const props = {
  gapStyle: { type: 'Style', desc: '间距样式' },
  config: { type: 'LIST_RENDER' }
}

(2) 方案 B

const props = {
  title: {
    type: String,
    desc: '主标题',
    default: '图片区'
  },
  productList: {
    type: Array,
    component: 'Uploadevent',
    desc: '图片列表',
    default: () => [
      {
        url: '',
        event: []
      }
    ]
  }
}

(3) 方案 C

{
  "title": "组件的配置",
  "description": "组件的描述",
  "type": "object",
  "properties": {
    "title": {
      "description": "标题",
      "type": "string",
      "maxLength": 10
    },
    "productList": {
      "type": "array",
      "items": {
        "url": {
          "description": "链接地址",
          "type": "string"
        },
        "events": {
          "type": "array",
          "description": "事件配置",
          "items": {
            "type": { "type": "string" },
            "title": { "type": "string" },
            "clickArea": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          "minItems": 0
        }
      },
      "required": ["url"],
      "minItems": 0
    }
  }
}

3. 递归

使用 $ref 关键字引用自己的子 schema 片段,实现递归模式,可以用于树形结构的描述

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "title": "$ref递归调用",
  "definitions": {
    "node": {
      "type": "object",
      "properties": {
        "children": {
          "type": "array",
          "items": {
            "$ref": "#/definitions/node"
          }
        }
      }
    }
  },
  "type": "object",
  "properties": {
    "tree": {
      "$ref": "#/definitions/node"
    }
  }
}

以下的 JSON 结构才能在上述的 JSON Schema 校验通过:

{
  "tree": {
    "children": [
      {
        "children": [
          {
            "children": []
          }
        ]
      }
    ]
  }
}

参考

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant