正在加载,请稍候…

JSON Schema:如何通过示例验证 JSON 数据

从零学习 JSON Schema,涵盖类型定义、必填字段、嵌套对象、数组、枚举和模式匹配。包含验证器、示例及常见验证错误。

JSON Schema:如何通过示例验证 JSON 数据

JSON Schema:验证并文档化你的 JSON 数据

JSON Schema 是一种用于注解和验证 JSON 文档的词汇表。它描述了数据的结构——哪些字段是必需的,它们必须是什么类型,以及必须满足哪些约束。可以把它看作是运行时数据的 TypeScript。

JSON Schema 用于:

  • 验证 API 请求/响应体
  • 在 OpenAPI 规范中文档化数据结构
  • 根据 schema 定义生成表单
  • 验证配置文件
  • 代码生成(从 schema 生成 TypeScript 类型)

JSON Schema:如何通过示例验证 JSON 数据 插图

基本 Schema 结构

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://example.com/schemas/user.json",
  "title": "User",
  "description": "A registered user in the system",
  "type": "object"
}
关键字 用途
$schema 指定使用的 JSON Schema 草案版本
$id 该 schema 的唯一标识符
title 人类可读的名称
description 文档说明
type 数据类型约束

基本类型

{ "type": "string" }
{ "type": "number" }
{ "type": "integer" }
{ "type": "boolean" }
{ "type": "null" }
{ "type": ["string", "null"] }

字符串约束

{
  "type": "string",
  "minLength": 1,
  "maxLength": 100,
  "pattern": "^[a-zA-Z0-9_]+
quot;, "format": "email" }

Format 值(验证取决于验证器库):

Format 验证内容
"email" 电子邮件地址
"uri" URI
"date" YYYY-MM-DD
"time" HH:MM:SS
"date-time" ISO 8601 日期时间
"uuid" UUID v1–v5
"ipv4" IPv4 地址
"ipv6" IPv6 地址
"hostname" DNS 主机名

数字约束

{
  "type": "number",
  "minimum": 0,
  "maximum": 100,
  "exclusiveMinimum": 0,
  "multipleOf": 0.01
}

对象

{
  "type": "object",
  "properties": {
    "id": {
      "type": "integer",
      "description": "Unique user ID"
    },
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 100
    },
    "email": {
      "type": "string",
      "format": "email"
    },
    "role": {
      "type": "string",
      "enum": ["admin", "editor", "viewer"]
    },
    "createdAt": {
      "type": "string",
      "format": "date-time"
    }
  },
  "required": ["id", "name", "email"],
  "additionalProperties": false
}

关键关键字:

  • properties:定义每个键的 schema
  • required:必须存在的键的数组
  • additionalProperties: false:拒绝不在 properties 中的额外字段
  • additionalProperties: { "type": "string" }:允许额外字段但约束其类型

JSON Schema:如何通过示例验证 JSON 数据 插图

属性数量

{
  "type": "object",
  "minProperties": 1,
  "maxProperties": 10
}

数组

{
  "type": "array",
  "items": { "type": "string" },
  "minItems": 1,
  "maxItems": 100,
  "uniqueItems": true
}

元组验证(固定长度数组)

{
  "type": "array",
  "prefixItems": [
    { "type": "string" },
    { "type": "number" },
    { "type": "boolean" }
  ],
  "items": false
}

这会验证 ["Alice", 30, true],但拒绝 ["Alice", 30, true, "extra"]

嵌套 Schema

{
  "type": "object",
  "properties": {
    "user": {
      "type": "object",
      "properties": {
        "name": { "type": "string" },
        "address": {
          "type": "object",
          "properties": {
            "street": { "type": "string" },
            "city": { "type": "string" },
            "country": { "type": "string", "minLength": 2, "maxLength": 2 }
          },
          "required": ["street", "city", "country"]
        }
      },
      "required": ["name"]
    },
    "tags": {
      "type": "array",
      "items": { "type": "string", "minLength": 1 },
      "uniqueItems": true
    }
  }
}

使用 $ref 重用 Schema

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$defs": {
    "Address": {
      "type": "object",
      "properties": {
        "street": { "type": "string" },
        "city": { "type": "string" }
      },
      "required": ["street", "city"]
    },
    "User": {
      "type": "object",
      "properties": {
        "name": { "type": "string" },
        "homeAddress": { "$ref": "#/$defs/Address" },
        "workAddress": { "$ref": "#/$defs/Address" }
      }
    }
  },
  "$ref": "#/$defs/User"
}

$defs(以前称为 definitions)保存可重用的子 schema。$ref 通过 JSON Pointer 引用它们。

JSON Schema:如何通过示例验证 JSON 数据 插图

组合 Schema

// oneOf:恰好一个匹配
{
  "oneOf": [
    { "type": "string" },
    { "type": "integer" }
  ]
}

// anyOf:一个或多个匹配
{
  "anyOf": [
    { "type": "string", "format": "email" },
    { "type": "string", "format": "uri" }
  ]
}

// allOf:全部匹配(用于组合/继承)
{
  "allOf": [
    { "$ref": "#/$defs/BaseUser" },
    {
      "properties": {
        "adminLevel": { "type": "integer", "minimum": 1, "maximum": 3 }
      },
      "required": ["adminLevel"]
    }
  ]
}

// not:必须不匹配
{
  "not": { "type": "string" }
}

条件验证(if/then/else)

{
  "type": "object",
  "properties": {
    "type": { "type": "string", "enum": ["individual", "business"] },
    "name": { "type": "string" },
    "companyName": { "type": "string" },
    "vatNumber": { "type": "string" }
  },
  "if": {
    "properties": { "type": { "const": "business" } }
  },
  "then": {
    "required": ["companyName", "vatNumber"]
  },
  "else": {
    "required": ["name"]
  }
}

在 JavaScript 中验证

AJV(推荐)

import Ajv from 'ajv';
import addFormats from 'ajv-formats';

const ajv = new Ajv({ allErrors: true });
addFormats(ajv);  // 添加 email, date-time, uri 等格式

const schema = {
  type: 'object',
  properties: {
    name: { type: 'string', minLength: 1 },
    email: { type: 'string', format: 'email' },
    age: { type: 'integer', minimum: 0, maximum: 150 },
  },
  required: ['name', 'email'],
  additionalProperties: false,
};

const validate = ajv.compile(schema);

const data = { name: 'Alice', email: 'alice@example.com', age: 30 };
const valid = validate(data);

if (!valid) {
  console.log(validate.errors);
  // [{
  //   instancePath: '/email',
  //   message: 'must match format "email"',
  //   ...
  // }]
}

Zod(TypeScript 优先,现代应用中更常见)

import { z } from 'zod';

const UserSchema = z.object({
  name: z.string().min(1).max(100),
  email: z.string().email(),
  age: z.number().int().min(0).max(150).optional(),
  role: z.enum(['admin', 'editor', 'viewer']),
});

type User = z.infer<typeof UserSchema>;  // 推导 TypeScript 类型

const result = UserSchema.safeParse({ name: 'Alice', email: 'bad-email', role: 'admin' });
if (!result.success) {
  result.error.issues.forEach(issue => {
    console.log(issue.path, issue.message);
    // ['email'] 'Invalid email'
  });
}

从 Zod 生成 JSON Schema

import { zodToJsonSchema } from 'zod-to-json-schema';

const jsonSchema = zodToJsonSchema(UserSchema, 'User');
// 从 Zod 定义生成标准 JSON Schema

OpenAPI 中的 JSON Schema

# openapi.yaml
paths:
  /users:
    post:
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateUserInput'

components:
  schemas:
    CreateUserInput:
      type: object
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 100
        email:
          type: string
          format: email
      required: [name, email]
      additionalProperties: false

→ 在浏览器中使用 JSON Viewer 探索和验证 JSON 数据结构。