JSON Schema 新手入門
實作導向的 JSON Schema 教學:型別、必填、約束、$ref、oneOf,附可直接複製貼上的範例與工具建議。
JSON 本身沒有 schema。兩份都合法的 JSON 文件可以完全長得不一樣——這 份彈性既是格式的優勢,也是生產上的最大風險。JSON Schema 就是描述 JSON 文件形狀的標準:可以機械地驗證、可以由它生成型別、可以給消費端 當文件用。
本文是實用入門:足以寫第一份 schema、做驗證、並了解涵蓋 95% 真實情境 的四五個運算子。
JSON Schema 是什麼
JSON Schema 本身就是一份 JSON 文件,描述「合法」JSON 文件的樣貌—— 哪些欄位必填、允許的型別、值的範圍、巢狀物件的形狀。
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"id": { "type": "string" },
"email": { "type": "string", "format": "email" }
},
"required": ["id", "email"]
}
驗證器 是接受 schema 與文件、判定是否符合並列出不符原因的函式庫。
JavaScript 主流兩款是 ajv(快、完全符合規格)與 zod(TypeScript
原生、但有自己的 schema 語言;可用 zod-to-json-schema 在兩者間互轉)。
第一份 schema
實用 schema 最少需要 type、properties、required:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"name": { "type": "string" },
"age": { "type": "integer" }
},
"required": ["name"]
}
用 ajv 驗證:
import Ajv from "ajv";
const ajv = new Ajv();
const validate = ajv.compile(schema);
console.log(validate({ name: "Ada", age: 36 })); // true
console.log(validate({ age: 36 })); // false
console.log(validate.errors); // [{ instancePath: "", message: "must have required property 'name'" }]
七個核心型別:string、number、integer、boolean、object、
array、null。type 可以是單一字串,或字串陣列以同時允許多型別。
字串與數字約束
字串:
minLength、maxLength——字元數(code unit,非 grapheme)。pattern——必須符合的正規表示式。format——具名格式,例如email、uri、date-time、uuid。在 ajv 中預設關閉,需加ajv-formats啟用。
{
"type": "string",
"minLength": 8,
"maxLength": 64,
"pattern": "^[A-Za-z0-9_]+$"
}
數字:
minimum、maximum——含界值。exclusiveMinimum、exclusiveMaximum——不含界值。multipleOf——必須為此數的倍數。
{
"type": "number",
"minimum": 0,
"exclusiveMaximum": 100,
"multipleOf": 0.01
}
陣列與巢狀物件
用 items 描述陣列元素,並可自由巢狀:
{
"type": "object",
"properties": {
"tags": {
"type": "array",
"items": { "type": "string" },
"minItems": 1,
"uniqueItems": true
},
"address": {
"type": "object",
"properties": {
"street": { "type": "string" },
"city": { "type": "string" }
},
"required": ["street", "city"]
}
}
}
重複使用的子 schema,用 $defs(舊稱 definitions)配合 $ref:
{
"$defs": {
"address": {
"type": "object",
"properties": {
"street": { "type": "string" },
"city": { "type": "string" }
},
"required": ["street", "city"]
}
},
"type": "object",
"properties": {
"billing": { "$ref": "#/$defs/address" },
"shipping": { "$ref": "#/$defs/address" }
}
}
enum、const 與選擇運算子
封閉值集合用 enum 或 const:
{
"type": "string",
"enum": ["pending", "active", "archived"]
}
「多選一形狀」(判別聯合)用 oneOf 或 anyOf:
{
"oneOf": [
{
"type": "object",
"properties": { "kind": { "const": "email" }, "address": { "type": "string", "format": "email" } },
"required": ["kind", "address"]
},
{
"type": "object",
"properties": { "kind": { "const": "phone" }, "number": { "type": "string" } },
"required": ["kind", "number"]
}
]
}
oneOf 要求 恰好一個 schema 符合;anyOf 要求至少一個。請優先
選擇有判別欄位的 oneOf,錯誤訊息會清楚很多。
additionalProperties 與嚴格度
JSON Schema 預設允許 未列在 properties 內的額外欄位。這對 API
請求 body 通常是錯的——欄位名稱打錯應該回 400,而非靜默忽略。
設成 additionalProperties: false 即可變嚴:
{
"type": "object",
"properties": { "name": { "type": "string" } },
"required": ["name"],
"additionalProperties": false
}
對自己控制的雙端 payload,嚴格是正解。對需要無痛演進的公開 API, 可預設寬鬆、再依版本逐步加嚴。
實務驗證
伺服端 endpoint 的兩階段流程:
JSON.parse請求 body。- 用 schema 驗證解析結果。
import Ajv from "ajv";
const ajv = new Ajv({ allErrors: true });
const validate = ajv.compile(schema);
app.post("/users", (req, res) => {
if (!validate(req.body)) {
return res.status(400).json({ errors: validate.errors });
}
// req.body 已確認符合 schema
});
對於前端與隨手驗證,JSON 驗證工具 接受 schema 與文件,並指出失敗的斷言。
延伸閱讀
- 什麼是 JSON?——schema 所建立於其上的語法規則。
- REST API 的 JSON 最佳實務——設計你要 schema 化的 payload。
- 如果想由 JSON 範本直接產出 TypeScript 型別(不用手寫 schema),請看 /zh-Hant/json/types/typescript。