程序员求职经验分享与学习资料整理平台

网站首页 > 文章精选 正文

搞懂JSON Schema:给数据加个“身份证”,不再“乱七八糟”!

balukai 2025-06-08 19:22:27 文章精选 4 ℃

JSON以其简洁、易读的特性广受欢迎,但当数据变得庞大、复杂,或者需要与多人协作时,你是不是也遇到过这些烦恼:

  • “前端传来的数据格式不对,导致后端报错了!”
  • “我的API文档写了一大堆,但别人还是不清楚数据该怎么传。”
  • “测试环境的数据和生产环境格式不一致,找bug找到头秃!”

别担心!今天,我们就来揭秘一个超级实用的“神器”——JSON Schema。它就像是给你的JSON数据加一个严格的“身份证”,定义数据的“长相”和“规则”,让数据传输和校验从此变得有章可循,告别“乱七八糟”!


一、什么是JSON Schema?数据的“蓝图”与“法典”

简单来说,JSON Schema 是一种基于 JSON 格式的 JSON 数据描述语言,是标准化的数据约定。 它可以用来:

  1. 定义 JSON 数据的结构: 明确每个字段的名称、类型、是否必需。
  2. 验证 JSON 数据的有效性: 检查传入的 JSON 数据是否符合预设的规则。
  3. 文档化 JSON API: 为你的 API 提供清晰的数据输入/输出规范。
  4. 自动化代码生成: 根据 Schema 自动生成数据模型或表单。
  • 官方标准:JSON Schema 由 json-schema.org 维护,有明确的版本规范(如 Draft-07、Draft-2020-12)。
  • 结构化规则:它通过定义数据类型、必填字段、取值范围、正则表达式等规则,约定 JSON 数据的格式。
  • 独立于编程语言:Schema 文件本身是一个 JSON 文件,与具体编程语言无关。

二、JSON Schema 的核心关键字:构建你的数据规则

JSON Schema 有一套丰富的关键字,让你能够精细地定义数据规则。我们来学习最常用的一些:

1. 基础类型(type):定义数据的基本属性

这是最基本的关键字,用于指定一个值应该是什么数据类型。

  • "string": 字符串 (例如: "hello", "123")
  • "number": 数字,可以是整数或浮点数 (例如: 123, 3.14)
  • "integer": 整数 (例如: 123)
  • "boolean": 布尔值 (true 或 false)
  • "array": 数组 (例如: [1, 2, 3])
  • "object": 对象 (例如: {"key": "value"})
  • "null": 空值 (null)

示例:

{
  "type": "string"
}

这个Schema表示:任何数据都必须是一个字符串。

2. 对象属性(properties, required):定义对象的结构

当你定义一个 type 为 "object" 的Schema时,这两个关键字就派上用场了。

  • properties: 定义对象中允许出现的属性及其对应的Schema。
  • required: 一个字符串数组,列出那些必须存在的属性名称。

示例:用户信息的Schema

我们想定义一个用户对象,包含 name (必需,字符串) 和 age (可选,整数)。

{
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "description": "用户的姓名"
    },
    "age": {
      "type": "integer",
      "description": "用户的年龄"
    }
  },
  "required": ["name"]
}

符合此Schema的数据:

JSON

{
  "name": "张三",
  "age": 30
}

JSON

{
  "name": "李四" // age 是可选的
}

不符合此Schema的数据:

{
  "age": 25 // 错误:缺少必需的 "name" 字段
}

3. 字符串限制(minLength, maxLength, pattern, format)

  • minLength: 字符串的最小长度。
  • maxLength: 字符串的最大长度。
  • pattern: 使用正则表达式来匹配字符串内容。
  • format: 预定义的字符串格式,例如 email, uri, date-time, ipv4 等。

示例:邮箱和密码的Schema

{
  "type": "object",
  "properties": {
    "email": {
      "type": "string",
      "format": "email",
      "description": "用户的邮箱地址"
    },
    "password": {
      "type": "string",
      "minLength": 6,
      "maxLength": 20,
      "pattern": "^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d]{6,20}#34;,
      "description": "密码,必须包含字母和数字,长度6-20位"
    }
  },
  "required": ["email", "password"]
}

4. 数字限制(minimum, maximum, exclusiveMinimum, exclusiveMaximum)

  • minimum: 最小值(包含)。
  • maximum: 最大值(包含)。
  • exclusiveMinimum: 最小值(不包含)。
  • exclusiveMaximum: 最大值(不包含)。

示例:年龄范围的Schema

{
  "type": "integer",
  "minimum": 18,
  "maximum": 60,
  "description": "年龄必须在18到60岁之间"
}

5. 数组限制(items, minItems, maxItems, uniqueItems)

  • items: 定义数组中每个元素应该符合的Schema。 如果 items 是一个Schema,则数组中的所有元素都必须符合该Schema。 如果 items 是一个数组,则它定义了按位置的元素Schema。
  • minItems: 数组的最小元素数量。
  • maxItems: 数组的最大元素数量。
  • uniqueItems: 如果设置为 true,数组中的所有元素必须是唯一的。

示例:商品列表的Schema

{
  "type": "array",
  "minItems": 1,
  "maxItems": 10,
  "uniqueItems": true,
  "items": {
    "type": "string",
    "description": "每个商品名称必须是唯一的字符串"
  }
}

6. 枚举(enum):定义允许的固定值

enum 关键字用于指定一个值必须是预定义列表中的一个。

示例:订单状态的Schema

{
  "type": "string",
  "enum": ["pending", "processing", "shipped", "delivered", "cancelled"],
  "description": "订单状态必须是预定义的值之一"
}

7. 组合逻辑(allOf, anyOf, oneOf, not):高级数据校验

这些关键字用于组合多个Schema,实现更复杂的逻辑。

  • allOf: 数据必须同时满足所有列出的Schema。
  • anyOf: 数据必须满足列出的至少一个Schema。
  • oneOf: 数据必须满足列出的且只能满足一个Schema。
  • not: 数据不能满足列出的Schema。

示例:多种用户类型的Schema (oneOf)

假设用户可以是“普通用户”或“管理员”,它们的字段不同。

{
  "type": "object",
  "oneOf": [
    {
      "properties": {
        "userType": { "const": "normal" },
        "points": { "type": "integer" }
      },
      "required": ["userType", "points"]
    },
    {
      "properties": {
        "userType": { "const": "admin" },
        "permissions": { "type": "array", "items": { "type": "string" } }
      },
      "required": ["userType", "permissions"]
    }
  ]
}

符合此Schema的数据:

{
  "userType": "normal",
  "points": 100
}
{
  "userType": "admin",
  "permissions": ["read", "write"]
}

不符合此Schema的数据:

{
  "userType": "normal",
  "permissions": ["read"] // 错误:同时包含普通用户和管理员的字段
}

三、JSON Schema 的相关规范要求和高级语法:理解它的严谨性

JSON Schema 并非随意制定,它遵循一套严格的规范,并提供了一些高级语法来处理更复杂的场景。

1. JSON Schema 版本声明 ($schema)

每个 JSON Schema 文件都应该包含 $schema 关键字,它指向所遵循的 JSON Schema 规范的 URI。这告诉解析器应该使用哪个版本的规范来解释你的 Schema。

  • 当前常用版本: draft-07(URI: http://json-schema.org/draft-07/schema#
  • 较新版本: 2019-09(URI: https://json-schema.org/draft/2019-09/schema) 和 2020-12(URI: https://json-schema.org/draft/2020-12/schema重要提示: 不同版本之间可能存在关键字的增删改动,或行为上的细微差别。在实际项目中,请保持 Schema 和验证器使用相同或兼容的版本。

示例:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "我的用户数据 Schema",
  "type": "object",
  // ... 其他属性
}

2. Schema 的根 $id 和引用 ($ref)

  • $id: 用于为 Schema 自身提供一个唯一标识符(URI)。这使得你可以在其他地方引用这个 Schema。
  • $ref: 用于引用另一个 Schema 或当前 Schema 中的某个部分。这对于构建可复用和模块化的 Schema 非常重要。

示例:定义可复用的地址Schema,并在用户Schema中引用它

address.json (地址Schema)

JSON

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "http://example.com/schemas/address.json",
  "title": "Address",
  "description": "用户地址信息",
  "type": "object",
  "properties": {
    "street": { "type": "string" },
    "city": { "type": "string" },
    "zipCode": { "type": "string", "pattern": "^\\d{6}#34; }
  },
  "required": ["street", "city", "zipCode"]
}

user.json (用户Schema,引用地址Schema)

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "http://example.com/schemas/user.json",
  "title": "User",
  "description": "用户信息",
  "type": "object",
  "properties": {
    "name": { "type": "string" },
    "email": { "type": "string", "format": "email" },
    "homeAddress": { "$ref": "http://example.com/schemas/address.json" },
    "workAddress": { "$ref": "http://example.com/schemas/address.json" }
  },
  "required": ["name", "email", "homeAddress"]
}

通过 $ref,user.json 重用了 address.json 中定义的地址结构,避免了重复编写,提高了可维护性。

3. 描述性关键字 (title, description, examples)

这些关键字不参与验证,但对提高 Schema 的可读性和文档性至关重要。

  • title: Schema 或其某个部分的短标题。
  • description: 对 Schema 或其某个部分的详细描述。
  • examples: 给出符合 Schema 的示例数据,方便理解。

示例:

JSON

{
  "type": "integer",
  "title": "商品库存数量",
  "description": "表示商品的当前库存量,必须是非负整数",
  "minimum": 0,
  "examples": [100, 0, 500]
}

4. 元数据和注释($comment)

$comment 是一个非验证关键字,用于在 Schema 中添加注释。它会被验证器忽略,但对于人类阅读和理解 Schema 非常有用。

示例:

{
  "type": "object",
  "properties": {
    "userId": {
      "type": "string",
      "$comment": "这是一个内部使用的用户ID,不应暴露给前端"
    },
    "username": { "type": "string" }
  }
}

5. 高级对象属性控制

  • patternProperties: 用于定义符合特定正则表达式模式的属性的Schema。当属性名不固定,但符合某种模式时非常有用。 示例:键名为数字字符串的属性
{
  "type": "object",
  "patternProperties": {
    "^[0-9]+#34;: { "type": "number", "minimum": 0 }
  },
  "additionalProperties": false,
  "description": "对象的所有属性名必须是数字字符串,且其值必须为非负数"
}

符合此Schema的数据: {"1": 10, "20": 5} 不符合此Schema的数据: {"abc": 10} (键名不匹配)

  • additionalProperties: 控制是否允许 Schema 中未明确定义的额外属性。
    • true (默认): 允许任何额外属性。
    • false: 不允许任何额外属性。
    • 一个Schema: 额外属性必须符合该Schema。 示例:只允许age和name,不能有其他属性
{
  "type": "object",
  "properties": {
    "name": { "type": "string" },
    "age": { "type": "integer" }
  },
  "additionalProperties": false
}

符合此Schema的数据: {"name": "小明", "age": 25} 不符合此Schema的数据: {"name": "小明", "age": 25, "city": "北京"} (多出city属性)

  • unevaluatedProperties (Draft 2019-09+): 类似 additionalProperties,但行为更智能,能与组合关键字(如 allOf)更好地配合,避免重复验证。它解决了 additionalProperties 在与组合关键字(如 allOf, anyOf, if/then/else)结合使用时可能出现的“漏网之鱼”问题。unevaluatedProperties 只会检查那些没有被任何其他关键字(如 properties, patternProperties, if/then/else 条件下的属性等)验证过的属性。这使得它在复杂场景下更加安全和灵活。

示例:unevaluatedProperties 的力量

假设我们有一个用户Schema,要求必须有 name 字段,并且如果 userType 是 admin,则必须有 permissions 字段。我们还想确保除了这些被明确或条件性验证的字段之外,不允许其他任何字段。

{
  "$schema": "https://json-schema.org/draft/2019-09/schema",
  "title": "高级用户类型",
  "type": "object",
  "properties": {
    "name": { "type": "string" },
    "userType": { "type": "string", "enum": ["normal", "admin"] }
  },
  "required": ["name", "userType"],
  "if": {
    "properties": { "userType": { "const": "admin" } }
  },
  "then": {
    "properties": {
      "permissions": { "type": "array", "items": { "type": "string" } }
    },
    "required": ["permissions"]
  },
  "unevaluatedProperties": false, // 关键在这里!
  "description": "用户必须有名称和类型。如果类型是'admin',则需要'permissions'字段。不允许任何其他未定义的属性。"
}

为什么 unevaluatedProperties: false 在这里更好? 如果使用 additionalProperties: false,它会简单地检查 properties 中未列出的所有属性。但 permissions 字段是在 if/then 结构中动态定义的,additionalProperties 可能无法正确识别它,从而导致验证失败。 而 unevaluatedProperties: false 会智能地等到所有可能的 Schema 都被“评估”过后,才检查剩余的、未被任何 Schema 规则处理过的属性。

符合此Schema的数据

{
  "name": "张三",
  "userType": "normal"
}
{
  "name": "管理员A",
  "userType": "admin",
  "permissions": ["read", "write"]
}

不符合此Schema的数据

{
  "name": "张三",
  "userType": "normal",
  "extraField": "value" // 错误:多余的 extraField,被 unevaluatedProperties: false 捕获
}
{
  "name": "管理员B",
  "userType": "admin" // 错误:缺少 permissions 字段
}
  • propertyNames: 定义对象属性名称本身必须满足的Schema。 示例:属性名称必须以"data_"开头
{
  "type": "object",
  "propertyNames": {
    "pattern": "^data_.*#34;
  },
  "description": "所有属性名必须以 'data_' 开头"
}

符合此Schema的数据: {"data_user": "Alice", "data_id": 123} 不符合此Schema的数据: {"user": "Bob"} (键名不匹配)

6. 高级数组控制

  • prefixItems (Draft 2019-09+): 替换 items 数组形式,用于按顺序校验数组的前N个元素。 示例:第一个元素是字符串,第二个是数字
{
  "type": "array",
  "prefixItems": [
    { "type": "string" },
    { "type": "number" }
  ],
  "items": { "type": "boolean" },
  "description": "数组前两个元素固定类型,后续元素为布尔值"
}

符合此Schema的数据: ["hello", 123, true, false]

  • contains: 数组中至少包含一个符合指定Schema的元素。 示例:数组中必须至少有一个偶数
{
  "type": "array",
  "items": { "type": "integer" },
  "contains": { "type": "integer", "multipleOf": 2 },
  "description": "数组中的元素都是整数,且至少有一个偶数"
}

符合此Schema的数据: [1, 3, 4, 5] 不符合此Schema的数据: [1, 3, 5] (没有偶数)

  • minContains, maxContains (Draft 2019-09+): 与 contains 结合使用,限制符合 contains Schema 的元素数量。

7. 条件校验 (if, then, else) (Draft 07+)

允许你根据某个条件Schema的验证结果,来应用不同的Schema。

示例:如果用户类型是"管理员",则必须有"权限"字段

{
  "type": "object",
  "properties": {
    "userType": { "type": "string", "enum": ["normal", "admin"] },
    "username": { "type": "string" },
    "permissions": { "type": "array", "items": { "type": "string" } }
  },
  "required": ["userType", "username"],
  "if": {
    "properties": { "userType": { "const": "admin" } }
  },
  "then": {
    "required": ["permissions"]
  },
  "else": {
    "not": { "required": ["permissions"] }
  },
  "description": "如果 userType 为 'admin',则 'permissions' 字段是必需的;否则 'permissions' 字段必须不存在"
}

符合此Schema的数据:

{ "userType": "normal", "username": "普通用户A" }
{ "userType": "admin", "username": "管理员B", "permissions": ["edit", "delete"] }

不符合此Schema的数据:

{ "userType": "admin", "username": "管理员C" } // 错误:缺少 permissions 字段
{ "userType": "normal", "username": "普通用户D", "permissions": ["view"] } // 错误:普通用户不应有 permissions 字段

8. 常量 (const) (Draft 06+)

指定一个值必须精确地等于某个常量。

示例:HTTP状态码必须是200

JSON

{
  "type": "integer",
  "const": 200,
  "description": "HTTP状态码必须是200"
}

9. 定义可复用组件 ($defs / definitions)

在较早的 Draft 版本中,这个关键字是 definitions。从 Draft 2019-09 开始,更推荐使用 $defs。它的作用是提供一个地方来定义可重用的 Schema 片段(或称为“子Schema”),这些片段可以在当前 Schema 或其他 Schema 中通过 $ref 引用。

使用 $defs 的好处是:

  • 模块化和复用性: 避免在 Schema 中重复编写相同的结构。
  • 可读性: 将复杂的 Schema 拆分成小的、易于理解的模块。
  • 维护性: 更改一个地方,所有引用它的地方都会生效。

示例:使用 $defs 定义可复用的地址和产品Schema

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "订单信息",
  "type": "object",
  "properties": {
    "orderId": { "type": "string", "pattern": "^ORD-\\d{8}#34; },
    "customer": {
      "type": "object",
      "properties": {
        "name": { "type": "string" },
        "email": { "type": "string", "format": "email" }
      },
      "required": ["name", "email"]
    },
    "shippingAddress": { "$ref": "#/$defs/Address" },  // 引用 $defs 中的 Address
    "items": {
      "type": "array",
      "items": { "$ref": "#/$defs/Product" }, // 引用 $defs 中的 Product
      "minItems": 1
    },
    "totalAmount": { "type": "number", "minimum": 0, "exclusiveMinimum": 0 }
  },
  "required": ["orderId", "customer", "shippingAddress", "items", "totalAmount"],

  "$defs": {  // 在这里定义可复用的子Schema
    "Address": {
      "type": "object",
      "properties": {
        "street": { "type": "string" },
        "city": { "type": "string" },
        "zipCode": { "type": "string", "pattern": "^\\d{6}#34; }
      },
      "required": ["street", "city", "zipCode"]
    },
    "Product": {
      "type": "object",
      "properties": {
        "productId": { "type": "string" },
        "productName": { "type": "string" },
        "quantity": { "type": "integer", "minimum": 1 },
        "price": { "type": "number", "minimum": 0, "exclusiveMinimum": 0 }
      },
      "required": ["productId", "productName", "quantity", "price"]
    }
  }
}

在这个例子中,Address 和 Product 这两个复杂的对象结构被定义在了 $defs 内部,然后在 properties 中通过 $ref 引用。# 代表当前 Schema,/defs/Address 则表示在当前 Schema 的 $defs 关键字下找到名为 Address 的定义。


四、JSON Schema 的实际应用:让开发更高效!

理解了这些核心关键字和规范要求,你就可以开始构建自己的 JSON Schema 了。那么,它在实际开发中到底有什么用呢?

  1. API 接口校验: 这是 JSON Schema 最常见的应用场景。后端接收到前端请求时,可以使用 Schema 验证请求体(Request Body)是否符合预设格式,不符合则直接拒绝,避免后续业务逻辑的错误。同样,后端返回给前端的数据也可以用 Schema 进行校验,确保数据完整性。
  2. 举例: 一个用户注册API,请求体需要 username, email, password。你可以用JSON Schema严格定义它们的类型、长度、格式,并在后端收到请求时第一时间验证。
  3. 数据持久化校验: 在将数据存储到数据库之前,使用 JSON Schema 进行校验,可以保证数据库中数据的质量和一致性。
  4. 配置文件的校验: 很多应用程序使用 JSON 作为配置文件。通过定义配置文件的 JSON Schema,可以确保配置的正确性,避免因配置错误导致程序崩溃。
  5. 自动化生成文档和客户端代码: 一些工具可以根据 JSON Schema 自动生成美观的 API 文档,甚至直接生成客户端(如前端)请求或响应的数据模型代码。这大大减少了手动编写文档和重复性代码的工作量。
  6. 想象一下: 你只需要维护一份 JSON Schema,就能同时更新 API 文档和客户端代码,是不是很酷?
  7. 数据转换与迁移: 在进行数据格式转换或系统迁移时,JSON Schema 可以作为“中间标准”,帮助你确保数据转换的正确性。

五、如何开始使用 JSON Schema?

  1. 选择你的JSON Schema版本: 目前推荐使用 draft-07 或更新版本。在你的Schema文件的顶部,通常会包含 $schema 关键字来指定版本。
  2. 编写你的Schema: 根据你的数据结构和校验需求,使用上述关键字来编写。
  3. 使用在线工具验证:
  4. JSON Schema Validator
  5. JSON Schema Lint 将你的Schema和待验证的JSON数据粘贴进去,即可看到验证结果。
  6. 在你的编程语言中使用验证库:

几乎所有主流编程语言都有成熟的 JSON Schema 验证库。

JavaScript: ajv (Another JSON Schema Validator)

Python: jsonschema

Java: json-schema-validator

Go: gojsonschema

这些库允许你在代码中加载 Schema,并对接收到的 JSON 数据进行编程化验证。

Python 示例:


六、总结与展望

JSON Schema 是一个强大的工具,能够帮助我们:

  • 提高数据质量: 确保数据的格式和内容符合预期。
  • 提升开发效率: 减少因数据格式问题导致的调试时间,加速API开发和集成。
  • 改善团队协作: 提供清晰的数据规范,让前后端、不同系统之间的协作更加顺畅。
  • 增强系统稳定性: 提前发现并阻止不合法数据进入系统。

通过本文的介绍,你不仅了解了 JSON Schema 的核心概念和常用关键字,还掌握了一些高级语法和规范要求,特别是$defs 这种强大的复用机制。在构建现代Web应用和微服务时,JSON Schema 已经成为了不可或缺的一部分。掌握它,你就掌握了数据管理的“金钥匙”,让你的项目更加健壮、高效!

现在,就去尝试定义你第一个JSON Schema,给你的数据一个“身份证”吧!你会发现,数据世界从此变得井然有序!

最近发表
标签列表