JSON Schema の draft 差分:04 / 06 / 07 / 2019-09 / 2020-12 を移行する前に知っておくこと

約9分

JSON Schema には draft-04 から最新の 2020-12 まで複数のリビジョンがあり、それぞれが微妙に互換性のない仕様変更を含んでいます。「ライブラリ A で書いた schema がライブラリ B で動かない」「OpenAPI を 3.0 から 3.1 に上げたら schema 検証が壊れた」は典型的なドラフト不整合です。本記事では各 draft の主な変更点と、移行で踏みやすい差分を整理します。

ドラフト一覧

Draft公開時期$schema URL の #
draft-042013http://json-schema.org/draft-04/schema#
draft-062017http://json-schema.org/draft-06/schema#
draft-072018http://json-schema.org/draft-07/schema#
draft 2019-092019https://json-schema.org/draft/2019-09/schema
draft 2020-122020https://json-schema.org/draft/2020-12/schema

draft-05 はスキップされ、2019 以降は YYYY-MM 形式に移行。

主要な変更点

id$id(draft-06)

draft-04 では schema 識別子が id キーで、$ref の解決基準にも使われていました。他のキーワードと混在しやすいため、draft-06 で $id に改名されました。

// draft-04
{ "id": "http://example.com/schema.json" }

// draft-06+
{ "$id": "http://example.com/schema.json" }

exclusiveMinimum / exclusiveMaximum(draft-06)

draft-04 では真偽値の補助:

// draft-04
{ "minimum": 0, "exclusiveMinimum": true }

draft-06 で数値そのものを受け取る形式に:

// draft-06+
{ "exclusiveMinimum": 0 }

draft-04 のスキーマを draft-06+ のバリデータに渡すと、exclusiveMinimum: true が「0 より大」ではなく「true より大」と解釈される可能性があります(多くのライブラリは互換モードを持つが、デフォルトでは厳密に新仕様で解釈)。

constcontainspropertyNames(draft-06)

新キーワードが追加:

{
	"const": "fixed-value", // draft-06: 単一値
	"contains": { "type": "string" }, // draft-06: 配列の少なくとも 1 要素にマッチ
	"propertyNames": { "pattern": "^[a-z]+$" } // draft-06: object のキー名を制約
}

if / then / else(draft-07)

条件分岐:

{
	"if": { "properties": { "country": { "const": "US" } } },
	"then": { "required": ["postalCode"] },
	"else": { "required": ["zip"] }
}

draft-06 まではこの種の条件は oneOf + 否定で表現する必要があり、複雑でした。

$ref と他キーの共存(2019-09)

draft-07 までは $ref を含むオブジェクトの他のキーは無視されるルールでした:

// draft-07: description は無視される
{
	"$ref": "#/definitions/User",
	"description": "ユーザー情報"
}

2019-09 で他キーも有効になるように変更。description や examples を $ref と並べて書けるようになりました。

unevaluatedProperties / unevaluatedItems(2019-09)

additionalProperties自身の properties で定義されていないキーを制御しますが、allOf で他のスキーマから来る properties は無視されます。unevaluatedPropertiesスキーマ全体(allOf 等の合成も含む)から見て未評価のプロパティを制御する新キーワード。

{
	"allOf": [{ "properties": { "name": { "type": "string" } } }],
	"unevaluatedProperties": false // name 以外を全て拒否
}

OpenAPI 3.1 や複雑なスキーマ合成で頻出。

$dynamicRef / $dynamicAnchor(2020-12)

$ref の動的版で、$ref の解決時に「呼び出し元のスキーマで定義された anchor を優先する」挙動。再帰的・拡張可能なスキーマ(自分自身を参照しつつ、子スキーマ側でフックを差し替えたい場合)で使います。

実装が複雑なため、サポートしているライブラリは主要なものに限られます(Ajv、JSV、json-everything 等)。

dependentRequired / dependentSchemas(2019-09)

draft-07 までの dependencies キーワードを 2 つに分割

// draft-07: 1 キーで両用
{ "dependencies": { "creditCard": ["billingAddress"] } }

// 2019-09+
{ "dependentRequired": { "creditCard": ["billingAddress"] } }

意味は同じだが命名が分かれた。

互換性マトリクス(実装側)

代表的なバリデータの draft 対応:

ライブラリdraft-04draft-06draft-072019-092020-12
Ajv (Node.js)✓(旧版)✓ (Ajv 8)✓ (Ajv 8)
jsonschema (Python)
everit-json-schema (Java)△(一部)
json-everything (.NET)
gojsonschema (Go)
santhosh-tekuri/jsonschema (Go)

Go の標準的なバリデータが 2019-09+ をサポートしていないことに注意。OpenAPI 3.1 を Go で扱うときの躓きポイント。

OpenAPI との関係

OpenAPI のバージョンと内部 JSON Schema の関係:

  • OpenAPI 3.0 → JSON Schema draft-05 ベース(実態はそれを微改造)
  • OpenAPI 3.1JSON Schema draft 2020-12 と完全互換

OpenAPI 3.0 の schema には nullable: true という独自フィールドがあり、これは JSON Schema 標準ではありません。OpenAPI 3.1 では type: ["string", "null"] のような正規の構文を使います。3.0 → 3.1 移行時に この置換が要ります。

移行時のチェックリスト

draft を上げるときに踏みやすい差分:

  1. id$id に書き換える
  2. exclusiveMinimum: true が残っていないか確認、exclusiveMinimum: <数値> に書き換え
  3. dependenciesdependentRequired / dependentSchemas に分離
  4. $ref と他キーの共存:description などが効くようになる(意図しない場合は別オブジェクトに分離)
  5. unevaluatedProperties: false を追加すべき箇所がないか(spread / allOf 合成があるとき)
  6. nullable: true (OpenAPI 3.0 専用) → type: [..., "null"] に変換
  7. バリデータライブラリが対象 draft をサポートしているか確認

実用上の指針

  • 新規プロジェクト → 2020-12 が第一候補(最新で、OpenAPI 3.1 と一致)
  • 既存システム互換 → 既存 schema の $schema に合わせ、その draft でロックイン
  • マルチ言語環境(Go、Java を含む)でバリデーションを共有 → draft-07 が最も互換性が高い(最大公約数)
  • OpenAPI 3.0 を維持 → schema は draft-05 もどき。3.1 に移行できるなら 2020-12 へ統一

まとめ

JSON Schema の draft 差分は「微妙に違う」ものが多く、とくに $idexclusiveMinimum$ref の挙動・unevaluatedProperties あたりは互換性を壊します。$schema の URL を必ず明記し、利用側のバリデータがその draft をサポートしていることを確認するのが最大公約数の安全策です。

JSON Schema を実際に書き、検証する作業は JSON バリデータツール で確認できます。