JSONスキーマバリデーション:決定版ガイド
JSON Schemaは、JSONドキュメントに注釈を付け、バリデーションするための語彙です。データがどのような形状であるべきか、どの型が許容されるか、どのフィールドが必須かを定義する契約を提供します。API、設定ファイル、または構造化データを扱う場合、JSON Schemaは不可欠なツールです。
なぜJSON Schemaを使うのか?
バリデーションなしでは、JSONデータは不確定要素です。年齢フィールドが文字列として来たり、メールが欠落したり、日付が間違ったフォーマットで来るかもしれません。JSON Schemaはこれらの問題を境界で捕捉し、不正なデータがシステムを伝播する前に防ぎます。
主なメリット:
- API契約の施行: リクエストとレスポンスのボディを自動的にバリデーション
- ドキュメント: スキーマがデータ構造の生きたドキュメントとして機能
- コード生成: ツールがJSON Schemaから型、フォーム、データベーススキーマを生成可能
- テスト: テストフィクスチャとモックデータをスキーマに対してバリデーション
基本的なスキーマ構造
すべてのJSON Schemaはいくつかの標準プロパティから始まります:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/user.schema.json",
"title": "User",
"description": "A registered user in the system",
"type": "object",
"properties": {
"id": { "type": "integer", "minimum": 1 },
"name": { "type": "string", "minLength": 1, "maxLength": 100 },
"email": { "type": "string", "format": "email" },
"age": { "type": "integer", "minimum": 0, "maximum": 150 }
},
"required": ["id", "name", "email"]
}
チームと共有する前にスキーマを読みやすくするには、JSONフォーマッターをご利用ください。
型制約
JSON Schemaは6つのプリミティブ型をサポートします:
| 型 | 説明 | 例 |
|---|---|---|
string | テキストデータ | "hello" |
number | 任意の数値 | 3.14 |
integer | 整数のみ | 42 |
boolean | 真偽値 | true |
array | 順序付きリスト | [1, 2, 3] |
object | キーバリューペア | {"key": "value"} |
null | Null値 | null |
文字列制約
{
"type": "string",
"minLength": 1,
"maxLength": 255,
"pattern": "^[A-Za-z0-9]+$",
"format": "email"
}
組み込みフォーマットには email、uri、date、date-time、ipv4、ipv6、uuid、hostname があります。
数値制約
{
"type": "number",
"minimum": 0,
"maximum": 100,
"exclusiveMinimum": 0,
"multipleOf": 0.01
}
配列バリデーション
配列はアイテム、長さ、一意性についてバリデーションできます:
{
"type": "array",
"items": { "type": "string", "format": "email" },
"minItems": 1,
"maxItems": 10,
"uniqueItems": true
}
タプルバリデーション(位置ごとに特定の型を持つ固定長配列):
{
"type": "array",
"prefixItems": [
{ "type": "number" },
{ "type": "string" },
{ "type": "boolean" }
],
"items": false
}
オブジェクトバリデーション
オブジェクトはプロパティバリデーション、必須フィールド、追加プロパティの制御をサポートします:
{
"type": "object",
"properties": {
"name": { "type": "string" },
"role": { "enum": ["admin", "editor", "viewer"] }
},
"required": ["name", "role"],
"additionalProperties": false
}
additionalProperties を false に設定すると、スキーマで定義されていないプロパティを拒否する厳格モードになります。これはフィールド名のタイポをキャッチしたいAPIに特に有用です。
合成キーワード
JSON Schemaは論理演算子を使用してスキーマを組み合わせることをサポートします:
allOf(AND)
データはすべてのサブスキーマに対して有効でなければなりません:
{
"allOf": [
{ "$ref": "#/$defs/baseUser" },
{ "properties": { "role": { "const": "admin" } } }
]
}
oneOf(XOR)
データは正確に1つのサブスキーマに対して有効でなければなりません:
{
"oneOf": [
{ "properties": { "type": { "const": "email" }, "email": { "type": "string" } } },
{ "properties": { "type": { "const": "phone" }, "phone": { "type": "string" } } }
]
}
anyOf(OR)
データは少なくとも1つのサブスキーマに対して有効でなければなりません。
条件付きバリデーション
if/then/else キーワードで条件付きバリデーションが可能です:
{
"type": "object",
"properties": {
"country": { "type": "string" },
"postalCode": { "type": "string" }
},
"if": {
"properties": { "country": { "const": "US" } }
},
"then": {
"properties": { "postalCode": { "pattern": "^[0-9]{5}(-[0-9]{4})?$" } }
},
"else": {
"properties": { "postalCode": { "pattern": "^[A-Z0-9 -]+$" } }
}
}
参照と再利用
$ref を使って再利用可能な定義を参照します:
{
"$defs": {
"address": {
"type": "object",
"properties": {
"street": { "type": "string" },
"city": { "type": "string" },
"country": { "type": "string" }
},
"required": ["street", "city", "country"]
}
},
"type": "object",
"properties": {
"billingAddress": { "$ref": "#/$defs/address" },
"shippingAddress": { "$ref": "#/$defs/address" }
}
}
実践でのバリデーション
JavaScript(Ajv)
const Ajv = require('ajv');
const ajv = new Ajv();
const validate = ajv.compile(schema);
const valid = validate(data);
if (!valid) console.log(validate.errors);
Python(jsonschema)
from jsonschema import validate, ValidationError
try:
validate(instance=data, schema=schema)
except ValidationError as e:
print(f"Validation error: {e.message}")
開発環境を構築せずにクイックバリデーションするには、JSONとスキーマをJSONバリデーターに貼り付けてください。
実践例:APIレスポンススキーマ
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"status": { "enum": ["success", "error"] },
"data": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "integer" },
"name": { "type": "string" },
"createdAt": { "type": "string", "format": "date-time" }
},
"required": ["id", "name"]
}
},
"pagination": {
"type": "object",
"properties": {
"page": { "type": "integer", "minimum": 1 },
"perPage": { "type": "integer", "minimum": 1, "maximum": 100 },
"total": { "type": "integer", "minimum": 0 }
}
}
},
"required": ["status", "data"]
}
FAQ
JSON Schema draft-07と2020-12の違いは何ですか?
Draft 2020-12ではいくつかの改善が導入されました:prefixItems が items のタプル形式に取って代わり、$dynamicRef がより柔軟な参照を可能にし、語彙サポートがカスタム拡張を許可します。新しいプロジェクトでは2020-12を使用してください。古いツールとの互換性のためには、draft-07が依然として広くサポートされています。
JSON Schemaは文字列フィールド内のネストされたJSONをバリデーションできますか?
直接的にはできません。JSON Schemaはパースされたjsonの構造をバリデーションします。フィールドにJSON文字列が含まれている場合、先にパースしてから別途バリデーションする必要があります。一部のバリデーターはこれを処理できるカスタムフォーマットバリデーターを提供していますが、標準の一部ではありません。
関連リソース
- JSONフォーマッター — JSONスキーマをフォーマット・整形
- JSONのバリデーション方法 — よくあるJSONバリデーションエラーと修正
- JSON APIデザインパターン — JSONでより良いREST APIを構築