JSON Schema: What It Is and How to Use It
Learn JSON Schema from scratch. Understand types, constraints, composition keywords, $ref, and see a complete practical example for API validation.
JSONスキーマとは?
JSONスキーマは、JSONデータの構造を記述するための語彙です。これを契約として考えてください:JSONドキュメントが持つべきフィールド、フィールドの型、満たすべき制約を定義します。ドキュメントがスキーマに準拠している場合、それは有効です。そうでない場合、バリデーターは何が間違っているかを正確に教えてくれます。
これは重要です。なぜなら、JSON自体は構造的に許容的だからです。JSON仕様には、APIが数値を期待するところで文字列を返したり、必須フィールドを省略したりすることを防ぐものはありません。JSONスキーマはそのギャップを埋めます。
JSONスキーマが重要な理由
- API契約。 APIが期待するものと返すものを正確に定義します。クライアントとサーバーチームは、ペイロードを独立して検証できます。
- データ検証。 フォーム送信、Webhookペイロード、設定ファイル、またはデータベース書き込みの境界で不正なデータをキャッチします。
- ドキュメント。 スキーマは機械可読のドキュメントです。ツールはそれから人間可読のドキュメント、フォーム、さらにはモックデータを生成できます。
- コード生成。 スキーマから直接TypeScriptインターフェース、Go構造体、またはPythonデータクラスを生成します。
基本:タイプ、プロパティ、必須
すべてのJSONスキーマはtypeから始まります。これは、バリデーターにトップレベルで期待される値の種類を伝えます:
{
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "ユーザーのフルネーム"
},
"age": {
"type": "integer",
"description": "年齢"
}
},
"required": ["name"]
}
このスキーマは、「nameプロパティ(文字列、必須)を持つJSONオブジェクトを期待し、オプションのageプロパティ(整数)を持つ」と言っています。nameが欠けているか、非文字列のnameを提供しているオブジェクトは無効です。
すべてのJSONスキーマタイプ
JSONスキーマは7つの基本タイプをサポートしています。以下はそれぞれの最小スキーマ例です:
| タイプ | 有効な値 | スキーマ例 |
|---|---|---|
string | "hello", "" | {"type": "string"} |
number | 3.14, -1, 0 | {"type": "number"} |
integer | 42, -7 | {"type": "integer"} |
boolean | true, false | {"type": "boolean"} |
null | null | {"type": "null"} |
object | {"key": "value"} | {"type": "object", "properties": {...}} |
array | [1, 2, 3] | {"type": "array", "items": {...}} |
オブジェクトの詳細
propertiesを使用して期待されるフィールドを定義し、requiredで必須のものをリストし、additionalPropertiesで追加フィールドが許可されるかどうかを制御します:
{
"type": "object",
"properties": {
"id": { "type": "integer" },
"email": { "type": "string", "format": "email" }
},
"required": ["id", "email"],
"additionalProperties": false
}
additionalProperties: falseを設定すると、propertiesにリストされていないキーを含むオブジェクトは拒否されます。これは厳密なAPI契約に役立ちます。
配列の詳細
itemsを使用して各要素がどのように見えるべきかを定義し、minItems / maxItemsで長さを制約します:
{
"type": "array",
"items": { "type": "string" },
"minItems": 1,
"maxItems": 10,
"uniqueItems": true
}
これは1から10のユニークな文字列の配列を受け入れます。空の配列や重複のある配列は検証に失敗します。
制約と検証キーワード
基本タイプを超えて、JSONスキーマは各タイプに対して細かい制約を提供します:
文字列制約
| キーワード | 説明 | 例 |
|---|---|---|
minLength | 最小文字数 | "minLength": 1 |
maxLength | 最大文字数 | "maxLength": 255 |
pattern | 文字列が一致する必要がある正規表現 | "pattern": "^[A-Z]{2}\\\\d{4}$" |
format | セマンティックフォーマットのヒント | "format": "email" |
enum | 許可される値 | "enum": ["active", "inactive"] |
数値制約
| キーワード | 説明 | 例 |
|---|---|---|
minimum | 値はこれ以上でなければならない | "minimum": 0 |
maximum | 値はこれ以下でなければならない | "maximum": 100 |
exclusiveMinimum | 値はこれより大きくなければならない | "exclusiveMinimum": 0 |
exclusiveMaximum | 値はこれより小さくなければならない | "exclusiveMaximum": 1000 |
multipleOf | 値はこれで割り切れる必要がある | "multipleOf": 0.01 |
スキーマの組み合わせ:allOf、anyOf、oneOf、not
構成キーワードを使用すると、単純なスキーマから複雑なスキーマを構築できます:
- allOf — データはすべてのリストされたスキーマに対して有効でなければなりません。複数の制約を組み合わせたり、基本スキーマを拡張したりするために使用されます。
- anyOf — データは少なくとも1つのリストされたスキーマに対して有効でなければなりません。複数のフォーマットを受け入れるフィールドに便利です。
- oneOf — データは正確に1つのリストされたスキーマに対して有効でなければなりません。識別されたユニオンに適しています。
- not — データは指定されたスキーマに対して無効でなければなりません。
{
"oneOf": [
{
"type": "object",
"properties": {
"type": { "const": "email" },
"address": { "type": "string", "format": "email" }
},
"required": ["type", "address"]
},
{
"type": "object",
"properties": {
"type": { "const": "phone" },
"number": { "type": "string", "pattern": "^\\\\+?[0-9]{7,15}$" }
},
"required": ["type", "number"]
}
]
}
このスキーマは、メール連絡先または電話連絡先のいずれかを受け入れますが、両方は受け入れません — タグ付きユニオンをモデル化するクリーンな方法です。
$refを使用した再利用性
スキーマはすぐに繰り返しになります。$refキーワードを使用すると、再利用可能な定義を参照できます:
{
"type": "object",
"properties": {
"billing_address": { "$ref": "#/$defs/address" },
"shipping_address": { "$ref": "#/$defs/address" }
},
"$defs": {
"address": {
"type": "object",
"properties": {
"street": { "type": "string" },
"city": { "type": "string" },
"zip": { "type": "string", "pattern": "^[0-9]{5}(-[0-9]{4})?$" }
},
"required": ["street", "city", "zip"]
}
}
}
billing_addressとshipping_addressは、重複なしで同じ構造を共有します。$defsセクション(古いドラフトではdefinitionsと呼ばれる)は、再利用可能なスキーマを保存するための慣例的な場所です。
完全な例:ユーザー登録の検証
以下は、ユーザー登録ペイロードを検証するための実際のスキーマです。これまでにカバーしたほとんどの機能を使用しています:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "UserRegistration",
"description": "POST /api/registerエンドポイントのスキーマ",
"type": "object",
"properties": {
"username": {
"type": "string",
"minLength": 3,
"maxLength": 30,
"pattern": "^[a-zA-Z0-9_]+$",
"description": "英数字のユーザー名、3-30文字"
},
"email": {
"type": "string",
"format": "email",
"maxLength": 254
},
"password": {
"type": "string",
"minLength": 8,
"maxLength": 128,
"description": "最低8文字"
},
"age": {
"type": "integer",
"minimum": 13,
"maximum": 150
},
"role": {
"type": "string",
"enum": ["user", "moderator"],
"default": "user"
},
"acceptedTerms": {
"type": "boolean",
"const": true
},
"tags": {
"type": "array",
"items": { "type": "string", "maxLength": 20 },
"maxItems": 5,
"uniqueItems": true
}
},
"required": ["username", "email", "password", "acceptedTerms"],
"additionalProperties": false
}
各フィールドに明確で強制可能な制約があることに注意してください。バリデーターは、2文字のユーザー名、13歳未満の年齢、重複タグ、またはスキーマにリストされていない余分なフィールドを拒否します。
自分で試してみてください: 任意のJSONを貼り付けて、JSONスキーマジェネレーターでスキーマを自動生成します。
実世界におけるJSONスキーマ
OpenAPI / Swagger
OpenAPIは、リクエストボディ、レスポンス形状、クエリパラメータを定義するためにJSONスキーマ(いくつかの拡張を含む)を使用します。OpenAPI仕様のすべてのschemaブロックはJSONスキーマです。OpenAPIドキュメントを書く場合、すでにJSONスキーマを書いています。
フォーム検証
react-jsonschema-formやJavaScript用の最速のJSONスキーマバリデーターであるajvのようなライブラリは、スキーマを使用してランタイムでフォームを生成および検証します。スキーマを一度定義すれば、バックエンドとフロントエンドの両方が同じルールに対して検証できます。
データベーススキーマ
MongoDBは、コレクションレベルでのJSONスキーマ検証をサポートしています。スキーマに準拠しないドキュメントを挿入または更新時に拒否する$jsonSchemaバリデーターを設定できます。
設定ファイル
VS Code、ESLint、その他多くのツールは、設定ファイルを検証するためにJSONスキーマを使用します。tsconfig.jsonを編集するときに得られる自動補完は、JSONスキーマによって提供されています。
ドラフトバージョンの比較
JSONスキーマは、いくつかのドラフトを通じて進化してきました。以下は、遭遇することになるドラフトです:
| ドラフト | 年 | 主要な追加機能 | ステータス |
|---|---|---|---|
| Draft-04 | 2013 | コア語彙、$ref、基本タイプ | レガシー(依然として広く使用されている) |
| Draft-06 | 2017 | const、contains、propertyNames | レガシー |
| Draft-07 | 2018 | if/then/else、readOnly、writeOnly | 広くサポートされている |
| 2019-09 | 2019 | $defs、unevaluatedProperties、dependentRequired | 主要なバリデーターによってサポートされている |
| 2020-12 | 2020 | prefixItems(タプル検証の置き換え)、動的$ref | 現行 / 推奨 |
新しいプロジェクトには2020-12を使用してください。既存のツールとの最大の互換性が必要な場合は、Draft-07が安全な選択です — ほぼ普遍的なライブラリサポートがあります。
避けるべき一般的な間違い
requiredを忘れる。propertiesの下に定義されたプロパティはデフォルトでオプションです。フィールドが存在する必要がある場合は、requiredにリストしてください。numberとintegerを混同する。numberは小数を受け入れますが、integerは受け入れません。ID、カウント、その他の整数値にはintegerを使用してください。- 早すぎる制約の過剰。 実際のエラーをキャッチする最小スキーマから始めてください。後で制約を厳しくすることはできますが、緩めることは破壊的な変更です。
$refを使用しない。 同じサブスキーマを複数の場所に複製することは、メンテナンスの悪夢です。共有構造を$defsに抽出してください。format検証を無視する。 デフォルトでは、ほとんどのバリデーターはformatを注釈として扱い、制約とは見なさない。フォーマット検証を明示的に有効にする必要があります(例:ajv.addFormat()または{ validateFormats: true }を渡す)。
自分で試してみてください: 任意のJSONサンプルからスキーマを生成し、JSONスキーマジェネレーターで制約をカスタマイズして要件に合わせてください。