JSON Schema Draft-07 详细解析

信息的自解释性越强,那么它将越通用。

30年前的XML和10年前的JSON能非常精确表达信息的结构和关系,所以能成为最通用的跨系统数据交换的标准,大多数人日用而不自知(我便是其中之一)。可扩展性是软件开发中绕不开的话题,为此,研发团队没少在这个方面投入心思。作为Web开发的重要标准JSON,本身就具有极强的可扩展性,然而在如何定义和约束JSON方面团队却闷着头造轮子,摆在那里十几年确未曾正眼看过,着实是我的疏忽。写这篇文章旨在系统地梳理JSON Schema要点,为将来的迭代改进作为参考依据。

0. JSON Schema 背景

以下是JSON Schema发展史的简要表格梳理:

发展阶段 时间范围 核心特点 重要版本/事件
早期探索 2007-2010 社区驱动,解决JSON数据格式一致性问题,初步提出基础类型和约束概念 首个公开提案出现;早期草案包含typerequired等基础关键字
规范成型 2011-2013 以"Draft"形式统一语法,明确"用JSON描述JSON"的设计理念,奠定基础框架 Draft 01(引入$schema);Draft 04(首个广泛认可的稳定版本,完善$ref等)
功能完善 2014-2019 扩展复杂场景验证能力,支持条件逻辑、格式校验,被主流技术生态接纳 Draft 06(新增const$id);Draft 07(引入if/then/else;OpenAPI 3.0采纳)
标准化推进 2019年至今 成立正式组织,规范迭代更结构化,扩展模块化和动态引用能力 JSON Schema Org成立;Draft 2020-12(重构核心模型,支持动态引用;被OpenAPI 3.1采纳)

如果不追求较为复杂的功能,Draft-07可以满足大多数定义数据和约束数据的使用场景。

1. 基础类型验证

1.1 type 关键字

定义数据的基本类型。

bash 复制代码
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "string"
}

支持的类型:

  • string - 字符串
  • number - 数字(整数或浮点数)
  • integer - 整数
  • boolean - 布尔值
  • array - 数组
  • object - 对象
  • null - 空值

多类型示例:

json 复制代码
{
  "type": ["string", "number"],
  "description": "可以是字符串或数字"
}

1.2 enum 枚举

限制值必须是预定义集合中的一个。

json 复制代码
{
  "type": "string",
  "enum": ["draft", "published", "archived"],
  "description": "文章状态"
}

实际用例:用户角色验证

json 复制代码
{
  "type": "object",
  "properties": {
    "role": {
      "type": "string",
      "enum": ["admin", "editor", "viewer"],
      "description": "用户角色"
    },
    "permissions": {
      "type": "array",
      "items": {
        "enum": ["read", "write", "delete", "share"]
      }
    }
  }
}

1.3 const 常量(Draft-06+)

值必须严格等于指定常量。

json 复制代码
{
  "const": "v1.0",
  "description": "API 版本必须是 v1.0"
}

2. 字符串验证

2.1 长度限制

json 复制代码
{
  "type": "string",
  "minLength": 3,
  "maxLength": 50,
  "description": "用户名:3-50 个字符"
}

2.2 pattern 正则表达式

json 复制代码
{
  "type": "string",
  "pattern": "^[a-zA-Z0-9_]+$",
  "description": "只允许字母、数字和下划线"
}

实际用例:表单验证

json 复制代码
{
  "type": "object",
  "properties": {
    "email": {
      "type": "string",
      "format": "email",
      "pattern": "^[^@]+@[^@]+\.[^@]+$"
    },
    "phone": {
      "type": "string",
      "pattern": "^\+?[1-9]\d{1,14}$",
      "description": "国际电话格式"
    },
    "zipCode": {
      "type": "string",
      "pattern": "^\d{5}(-\d{4})?$",
      "description": "美国邮政编码"
    }
  }
}

2.3 format 格式

Draft-07 支持的常见格式:

json 复制代码
{
  "type": "object",
  "properties": {
    "email": {"type": "string", "format": "email"},
    "website": {"type": "string", "format": "uri"},
    "createdAt": {"type": "string", "format": "date-time"},
    "birthDate": {"type": "string", "format": "date"},
    "ipAddress": {"type": "string", "format": "ipv4"},
    "ipv6": {"type": "string", "format": "ipv6"}
  }
}

3. 数字验证

3.1 范围限制

json 复制代码
{
  "type": "number",
  "minimum": 0,
  "maximum": 100,
  "exclusiveMinimum": 0,
  "exclusiveMaximum": 100
}

注意: Draft-07 中 exclusiveMinimumexclusiveMaximum 是布尔值,与 minimum/maximum 配合使用。

3.2 multipleOf 倍数

json 复制代码
{
  "type": "number",
  "multipleOf": 0.5,
  "description": "必须是 0.5 的倍数"
}

实际用例:价格和数量

json 复制代码
{
  "type": "object",
  "properties": {
    "price": {
      "type": "number",
      "minimum": 0,
      "multipleOf": 0.01,
      "description": "价格,精确到分"
    },
    "quantity": {
      "type": "integer",
      "minimum": 1,
      "maximum": 999,
      "description": "购买数量"
    },
    "discount": {
      "type": "number",
      "minimum": 0,
      "maximum": 1,
      "exclusiveMaximum": true,
      "description": "折扣率 0-1 之间"
    }
  }
}

4. 对象验证

4.1 properties 和 required

json 复制代码
{
  "type": "object",
  "properties": {
    "name": {"type": "string"},
    "age": {"type": "integer", "minimum": 0},
    "email": {"type": "string", "format": "email"}
  },
  "required": ["name", "email"]
}

4.2 additionalProperties

控制是否允许未定义的属性。

json 复制代码
{
  "type": "object",
  "properties": {
    "id": {"type": "integer"}
  },
  "additionalProperties": false
}

允许额外属性但限制类型:

json 复制代码
{
  "type": "object",
  "properties": {
    "name": {"type": "string"}
  },
  "additionalProperties": {"type": "string"}
}

4.3 propertyNames

验证属性名称。

json 复制代码
{
  "type": "object",
  "propertyNames": {
    "pattern": "^[a-z]+$"
  },
  "description": "所有属性名必须是小写字母"
}

4.4 minProperties 和 maxProperties

json 复制代码
{
  "type": "object",
  "minProperties": 1,
  "maxProperties": 5,
  "description": "对象必须有 1-5 个属性"
}

实际用例:API 配置对象

json 复制代码
{
  "type": "object",
  "properties": {
    "host": {
      "type": "string",
      "format": "hostname"
    },
    "port": {
      "type": "integer",
      "minimum": 1,
      "maximum": 65535
    },
    "timeout": {
      "type": "integer",
      "minimum": 0,
      "default": 30000
    },
    "retries": {
      "type": "integer",
      "minimum": 0,
      "maximum": 10,
      "default": 3
    }
  },
  "required": ["host"],
  "additionalProperties": false
}

4.5 dependencies

属性依赖关系。

简单依赖:

json 复制代码
{
  "type": "object",
  "properties": {
    "creditCard": {"type": "string"},
    "billingAddress": {"type": "string"}
  },
  "dependencies": {
    "creditCard": ["billingAddress"]
  },
  "description": "如果有信用卡,必须提供账单地址"
}

模式依赖:

json 复制代码
{
  "type": "object",
  "properties": {
    "type": {"enum": ["personal", "business"]}
  },
  "dependencies": {
    "type": {
      "oneOf": [
        {
          "properties": {
            "type": {"const": "personal"},
            "firstName": {"type": "string"},
            "lastName": {"type": "string"}
          },
          "required": ["firstName", "lastName"]
        },
        {
          "properties": {
            "type": {"const": "business"},
            "companyName": {"type": "string"},
            "taxId": {"type": "string"}
          },
          "required": ["companyName", "taxId"]
        }
      ]
    }
  }
}

5. 数组验证

5.1 items

定义数组元素的模式。

单一模式(所有元素相同):

json 复制代码
{
  "type": "array",
  "items": {
    "type": "string"
  }
}

元组验证(每个位置不同模式):

json 复制代码
{
  "type": "array",
  "items": [
    {"type": "string"},
    {"type": "number"},
    {"type": "boolean"}
  ],
  "additionalItems": false,
  "description": "[姓名, 年龄, 是否激活]"
}

5.2 contains(Draft-06+)

数组必须包含至少一个符合模式的元素。

json 复制代码
{
  "type": "array",
  "contains": {
    "type": "number",
    "minimum": 5
  },
  "description": "数组必须包含至少一个大于等于 5 的数字"
}

5.3 长度和唯一性

json 复制代码
{
  "type": "array",
  "minItems": 1,
  "maxItems": 10,
  "uniqueItems": true,
  "description": "1-10 个唯一元素"
}

实际用例:标签系统

json 复制代码
{
  "type": "object",
  "properties": {
    "tags": {
      "type": "array",
      "items": {
        "type": "string",
        "pattern": "^[a-z0-9-]+$",
        "minLength": 2,
        "maxLength": 30
      },
      "minItems": 1,
      "maxItems": 10,
      "uniqueItems": true,
      "description": "文章标签"
    }
  }
}

6. 条件验证 (if/then/else) - Draft-07 新特性

这是 Draft-07 最强大的新特性之一。

6.1 基础用法

json 复制代码
{
  "type": "object",
  "properties": {
    "country": {"type": "string"}
  },
  "if": {
    "properties": {
      "country": {"const": "USA"}
    }
  },
  "then": {
    "properties": {
      "zipCode": {
        "type": "string",
        "pattern": "^\d{5}$"
      }
    },
    "required": ["zipCode"]
  },
  "else": {
    "properties": {
      "postalCode": {"type": "string"}
    }
  }
}

6.2 实际用例:动态表单验证

json 复制代码
{
  "type": "object",
  "properties": {
    "accountType": {
      "enum": ["personal", "business"]
    },
    "age": {"type": "integer"},
    "company": {"type": "string"},
    "taxId": {"type": "string"}
  },
  "required": ["accountType"],
  "if": {
    "properties": {
      "accountType": {"const": "personal"}
    }
  },
  "then": {
    "properties": {
      "age": {
        "type": "integer",
        "minimum": 18
      }
    },
    "required": ["age"]
  },
  "else": {
    "required": ["company", "taxId"],
    "properties": {
      "taxId": {
        "type": "string",
        "pattern": "^\d{2}-\d{7}$"
      }
    }
  }
}

6.3 多条件嵌套

json 复制代码
{
  "type": "object",
  "properties": {
    "shippingMethod": {
      "enum": ["standard", "express", "international"]
    },
    "weight": {"type": "number"}
  },
  "if": {
    "properties": {
      "shippingMethod": {"const": "international"}
    }
  },
  "then": {
    "properties": {
      "customsValue": {
        "type": "number",
        "minimum": 0
      },
      "destinationCountry": {
        "type": "string",
        "minLength": 2
      }
    },
    "required": ["customsValue", "destinationCountry"]
  },
  "else": {
    "if": {
      "properties": {
        "shippingMethod": {"const": "express"}
      }
    },
    "then": {
      "properties": {
        "weight": {
          "type": "number",
          "maximum": 30
        }
      }
    }
  }
}

7. 组合模式

7.1 allOf(必须满足所有)

json 复制代码
{
  "allOf": [
    {
      "type": "object",
      "properties": {
        "name": {"type": "string"}
      },
      "required": ["name"]
    },
    {
      "properties": {
        "age": {
          "type": "integer",
          "minimum": 0
        }
      }
    }
  ]
}

7.2 anyOf(至少满足一个)

json 复制代码
{
  "anyOf": [
    {"type": "string", "minLength": 5},
    {"type": "number", "minimum": 0}
  ],
  "description": "必须是长度>=5的字符串或非负数"
}

7.3 oneOf(恰好满足一个)

css 复制代码
{
  "oneOf": [
    {
      "properties": {
        "type": {"const": "email"},
        "address": {
          "type": "string",
          "format": "email"
        }
      },
      "required": ["type", "address"]
    },
    {
      "properties": {
        "type": {"const": "phone"},
        "number": {
          "type": "string",
          "pattern": "^\+?[0-9]{10,15}$"
        }
      },
      "required": ["type", "number"]
    }
  ]
}

7.4 not(不能满足)

json 复制代码
{
  "not": {
    "properties": {
      "role": {"const": "admin"}
    }
  },
  "description": "角色不能是 admin"
}

实际用例:多态数据验证

bash 复制代码
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "支付方式",
  "type": "object",
  "required": ["paymentType"],
  "properties": {
    "paymentType": {
      "type": "string",
      "enum": ["credit_card", "paypal", "bank_transfer"]
    }
  },
  "oneOf": [
    {
      "properties": {
        "paymentType": {"const": "credit_card"},
        "cardNumber": {
          "type": "string",
          "pattern": "^[0-9]{16}$"
        },
        "cvv": {
          "type": "string",
          "pattern": "^[0-9]{3,4}$"
        },
        "expiryDate": {
          "type": "string",
          "pattern": "^(0[1-9]|1[0-2])/[0-9]{2}$"
        }
      },
      "required": ["cardNumber", "cvv", "expiryDate"]
    },
    {
      "properties": {
        "paymentType": {"const": "paypal"},
        "email": {
          "type": "string",
          "format": "email"
        }
      },
      "required": ["email"]
    },
    {
      "properties": {
        "paymentType": {"const": "bank_transfer"},
        "accountNumber": {"type": "string"},
        "routingNumber": {"type": "string"}
      },
      "required": ["accountNumber", "routingNumber"]
    }
  ]
}

8. $ref 引用

8.1 内部引用

bash 复制代码
{
  "definitions": {
    "address": {
      "type": "object",
      "properties": {
        "street": {"type": "string"},
        "city": {"type": "string"},
        "zipCode": {"type": "string"}
      },
      "required": ["street", "city"]
    }
  },
  "type": "object",
  "properties": {
    "billingAddress": {"$ref": "#/definitions/address"},
    "shippingAddress": {"$ref": "#/definitions/address"}
  }
}

8.2 外部引用

bash 复制代码
{
  "type": "object",
  "properties": {
    "user": {
      "$ref": "user-schema.json"
    }
  }
}

8.3 递归引用

bash 复制代码
{
  "definitions": {
    "node": {
      "type": "object",
      "properties": {
        "value": {"type": "string"},
        "children": {
          "type": "array",
          "items": {"$ref": "#/definitions/node"}
        }
      }
    }
  },
  "$ref": "#/definitions/node"
}

9. 元数据关键字

9.1 描述性关键字

json 复制代码
{
  "title": "用户配置",
  "description": "用户账户的配置选项",
  "examples": [
    {
      "username": "john_doe",
      "email": "john@example.com"
    }
  ],
  "default": {
    "theme": "light",
    "notifications": true
  }
}

9.2 $comment(Draft-07 新增)

用于内部注释,不影响验证。

bash 复制代码
{
  "type": "object",
  "$comment": "这个模式用于验证用户输入,版本 2.0",
  "properties": {
    "age": {
      "type": "integer",
      "$comment": "TODO: 考虑添加最大年龄限制"
    }
  }
}

9.3 readOnly 和 writeOnly(Draft-07 新增)

json 复制代码
{
  "type": "object",
  "properties": {
    "id": {
      "type": "integer",
      "readOnly": true,
      "description": "只读字段,由系统生成"
    },
    "password": {
      "type": "string",
      "writeOnly": true,
      "description": "只写字段,不在响应中返回"
    },
    "username": {
      "type": "string"
    }
  }
}

10. 扩展

1. 扩展的基本原则

JSON Schema 允许在模式中添加任何自定义属性,只要遵循以下规则:

✅ 允许的做法

json 复制代码
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "string",
  
  // 标准关键字
  "minLength": 3,
  
  // ⭐ 自定义扩展(不以 $ 开头)
  "x-ui-widget": "textarea",
  "x-database-column": "user_email",
  "customFieldId": "field_123"
}

❌ 禁止的做法

json 复制代码
{
  // ❌ 不要使用 $ 开头(保留给规范)
  "$myCustomField": "value",
  
  // ❌ 不要与标准关键字冲突
  "type": "custom-type"  // type 只能是标准类型
}

2. 命名约定

推荐的命名规范

方式 1:使用 x- 前缀(最常见)

json 复制代码
{
  "type": "string",
  "x-display-name": "用户邮箱",
  "x-validation-level": "strict",
  "x-internal-id": "USR_EMAIL_001"
}

方式 2:使用厂商前缀

json 复制代码
{
  "type": "object",
  "aabbcc-encryption": "AES-256",
  "aabbcc-retention-days": 90,
  "microsoft-sql-type": "NVARCHAR(255)"
}

方式 3:使用域名前缀(最安全)

json 复制代码
{
  "type": "string",
  "com.mycompany.ui.widget": "date-picker",
  "com.mycompany.db.indexed": true
}

11. 验证

Schema 标准验证

扩展验证实现

如果项目中扩展了Schema,那么就要为此制定扩展验证。利用标准验证器会忽略Schema以外的定义的这一特性,承接自定义特性的判断逻辑。

JavaScript 复制代码
// 自定义验证器示例
class ExtendedValidator {
  constructor(schema) {
    this.schema = schema;
    this.standardValidator = new StandardJSONSchemaValidator();
  }
  
  async validate(data) {
    // 1. 先执行标准验证
    const standardErrors = this.standardValidator.validate(data, this.schema);
    if (standardErrors.length > 0) {
      return standardErrors;
    }
    
    // 2. 执行自定义扩展验证
    const customErrors = [];
    
    // 处理异步验证
    if (this.schema.properties.username?.['x-validation-async']) {
      const isUnique = await this.checkUsernameUniqueness(data.username);
      if (!isUnique) {
        customErrors.push({
          field: 'username',
          message: this.schema.properties.username['x-error-messages']?.async
        });
      }
    }
    
    // 处理密码强度
    if (this.schema.properties.password?.['x-password-rules']) {
      const passwordErrors = this.validatePasswordRules(
        data.password,
        this.schema.properties.password['x-password-rules']
      );
      customErrors.push(...passwordErrors);
    }
    
    // 处理业务规则
    if (this.schema['x-business']?.['workflow']) {
      const workflowErrors = await this.validateBusinessWorkflow(data);
      customErrors.push(...workflowErrors);
    }
    
    return customErrors;
  }
  
  async checkUsernameUniqueness(username) {
    // 调用 API 检查唯一性
    const response = await fetch(`/api/check-username?username=${username}`);
    return response.json().then(data => data.available);
  }
  
  validatePasswordRules(password, rules) {
    const errors = [];
    
    if (rules['require-uppercase'] && !/[A-Z]/.test(password)) {
      errors.push({field: 'password', message: 'Must contain uppercase letter'});
    }
    
    if (rules['require-lowercase'] && !/[a-z]/.test(password)) {
      errors.push({field: 'password', message: 'Must contain lowercase letter'});
    }
    
    // ... 更多规则检查
    
    return errors;
  }
}

总结

  1. 不要重复造轮子,做之前最好先快速调研现有的技术,往往会得到现成且成熟的答案(虽然不是100%匹配);
  2. Schema Draft-07已经足够好,稍加补充就能完美匹配需求;
  3. 团队内达成共识比技术选型更重要,大家步调一致才能合作创新;

参考资源:

相关推荐
AndrewHZ2 小时前
【图像处理基石】GIS图像处理入门:4个核心算法与Python实现(附完整代码)
图像处理·python·算法·计算机视觉·gis·cv·地理信息系统
U.2 SSD3 小时前
ECharts漏斗图示例
前端·javascript·echarts
江城开朗的豌豆3 小时前
我的小程序登录优化记:从短信验证到“一键获取”手机号
前端·javascript·微信小程序
excel3 小时前
Vue Mixin 全解析:概念、使用与源码
前端·javascript·vue.js
杨小码不BUG3 小时前
蛇形舞动:矩阵填充的艺术与算法(洛谷P5731)
c++·算法·矩阵·csp-j/s·循环控制
MicroTech20253 小时前
微算法科技(NASDAQ:MLGO)开发延迟和隐私感知卷积神经网络分布式推理,助力可靠人工智能系统技术
人工智能·科技·算法
Never_Satisfied3 小时前
在JavaScript / HTML中,Chrome报错此服务器无法证实它就是xxxxx - 它的安全证书没有指定主题备用名称
javascript·chrome·html
艾小码3 小时前
零基础学JavaScript:手把手带你搭建环境,写出第一个程序!
javascript
Boop_wu4 小时前
[数据结构] Map和Set
java·数据结构·算法