JSON Toolkit

クエリと検証 FAQ:JSONPath と Schema

要点:パスクエリは JSONPath か Pointer か、Patch と Merge Patch の選び方、Schema が止められるもの、ドラフト版の選び方。一度に解説します。

中級約 12 分で読めます2026-10-30 公開2026-10-30 更新
クエリと検証 FAQ のカバー画像

要点:条件でノードを見つけるには JSONPath、正確な位置を指定・編集するには JSON Pointer か JSON Patch、設定をマージするには Merge Patch を使いましょう。新しい Schema は 2020-12 に固定し、allErrors を有効にしてすべての失敗を一度に返します。

クエリは「見つける」、検証は「正しいと確認する」です。それぞれ独自の標準があるので、混ぜないでください。

1. パスクエリ

JSONPath と JSON Pointer はどう選べばよいですか?

「条件で探す」のか「位置で取得する」のかに依存します。

観点 JSONPath JSON Pointer
標準 RFC 9535(2024) RFC 6901(2013)
式 $.users[?(@.age > 18)].id /users/0/id
ワイルドカードと再帰 *、.. に対応 非対応
条件フィルタ 対応 非対応
結果 ノード集合(複数のことも) 単一の位置
表現力 強いが、実装差の重い遺産を持つ 弱いが完全に決定的
典型的な用途 ログ抽出、デバッグ、データ探索 JSON Patch、設定の位置指定、エラーパス

選択規則は単純です。

  • 「条件に一致するすべてのノードを取得」→ JSONPath。
  • 「正確な位置を一つ固定し、追加・削除・変更」→ JSON Pointer。
  • 併用する:JSONPath でノードを見つけ、Pointer でその位置を記述する。

API 契約の位置指定に JSONPath を使わないでください。JSONPath は集合を返し、空・単一要素・複数要素の三つの分岐が プロトコルを複雑にします。Pointer は「既存の位置を指すか、存在しないか」で、契約に向きます。

$.. と $.* の違いは?

$.. は再帰降下で全階層を歩き、$.* は現在の階層の直接の子だけを返します。

{
  "a": { "id": 1, "child": { "id": 2 } },
  "b": { "id": 3 }
}
  • $.* → 二つのオブジェクト {id:1, child:{...}} と {id:3}。
  • $..id → 三つの値 1、2、3。
  • $.*.id → 1 と 3。ネストした 2 は含まない。

再帰降下は便利ですが高コストです。$.. はツリー全体を歩き、大きな文書では O(n) で、意図しない深いフィールドを簡単に掬い上げます。 具体的な階層で動くなら .. を避けてください。

フィルタは文字列を比較できますか?正規表現で一致できますか?

文字列比較は動きます。正規表現は実装に依存します。

標準構文(RFC 9535):

$.items[?(@.status == "active")]
$.items[?(@.price > 10 && @.stock > 0)]
$.items[?(@.name == $.target)]      // 別のノードと比較

文字列比較は二重引用符を使います(RFC 9535 では単一引用符も許されますが、一部の古い実装は二重引用符しか受け付けません)。 数値比較は数値の意味論に従います。文字列と数値の混在、たとえば "10" > 9 は標準では未定義の振る舞いなので、当てにしないでください。

正規表現については、RFC 9535 が match() と search() 関数を定義しますが、実装間の対応差はかなり大きいです。 安全な方法:JSONPath では粗いフィルタだけを行い、結果を取得した後にコードで正規表現照合を行います。

JSON Patch と Merge Patch のどちらを使うべきですか?

どちらも「どう変えるか」を記述しますが、表現力と意味論はまったく異なります。

観点 JSON Patch(RFC 6902) Merge Patch(RFC 7386)
形 操作の配列 [{"op":"add",...}] 部分オブジェクト
対応操作 add / remove / replace / move / copy / test マージのみ(削除は null 経由)
配列要素を指定できるか はい。インデックスで いいえ。配列全体が置換される
原子性 test が楽観的ロックを可能に なし
文書サイズ 大きいが正確 小さいが表現力が限られる
向く場面 細かい編集、API の部分更新 設定のマージ、フォームの部分送信

判断基準:

  • 配列の 3 番目の要素のフィールドを変える → JSON Patch。
  • 複数の設定項目を既存設定にマージする → Merge Patch。
  • 「元の値が X でなければ失敗」という並行性制御 → JSON Patch の test。

Merge Patch の null 意味論の罠に注意してください。null は「このキーを削除」であり、「値を null に設定」ではありません。 値を null に設定することは Merge Patch では不可能です。

2. Schema 検証

JSON Schema はどんな問題を解きますか?

「データがどう見えるべきか」を口頭の合意から機械が検査できる契約に変えます。実際に止められるエラー:

  • 必須フィールドの欠落(required)。
  • 型違い。数値が期待される場所の文字列など(type)。
  • 範囲外の値(minimum / maximum / minLength)。
  • 許可集合の外の値(enum)。
  • 配列要素の型の不整合や誤った配列長(items / minItems)。
  • ネストした構造のフィールド欠落(properties を通じた再帰検証)。
  • 余分なフィールド(additionalProperties: false)。

解けないもの:業務規則(「終了時刻は開始時刻より後でなければならない」)、フィールドをまたぐ条件(一部は if/then で表現できるが 可読性が損なわれる)、データベースの一意性、権限チェック。

ですから Schema は「データの形」の門番であり、業務ロジックの代替ではありません。

draft-07 と 2020-12 のどちらを使うべきですか?

新規プロジェクトには 2020-12 を使い、既存システムではバリデータの対応を確認します。

バージョン 状態 主な変更
draft-04 古い 多くの古いライブラリがなお既定
draft-07 事実上の標準 最も広い対応、最も豊かなエコシステム
2019-09 過渡的 $defs、unevaluatedProperties を導入
2020-12 現在推奨 items が prefixItems + items に分割、$dynamicRef 対応

移行の二つの罠:

  1. タプル構文の変更:draft-07 では配列値の items がタプルを表現しました。2020-12 ではそれを prefixItems に移しました。
  2. $ref と兄弟キーワード:draft-07 では $ref があると兄弟キーワードが無視され、2020-12 では共存を許します。

バリデータが draft-07 しか対応していないのに 2020-12 構文を書くと、静かに無視されます。最も危険なのは「何も検証されていないのに検証が通る」場合です。

Schema から TypeScript 型を生成するには?

二つの経路:Schema から型を導出するか、型から Schema を生成するか。

Schema から型へ(バックエンドが契約を定義し、フロントエンドが消費する):

import type { FromSchema } from 'json-schema-to-ts';

const userSchema = {
  type: 'object',
  properties: {
    id: { type: 'string' },
    age: { type: 'integer' },
  },
  required: ['id'],
  additionalProperties: false,
} as const;

type User = FromSchema<typeof userSchema>;
// { id: string; age?: number }

鍵は as const です。なければ type: 'string' がリテラル 'string' ではなく string と推論され、推論が壊れます。

型から Schema へ(フロントエンドにすでに型があり、実行時検証が必要):

import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';

const userSchema = z.object({ id: z.string(), age: z.number().int().optional() });
const jsonSchema = zodToJsonSchema(userSchema);

この経路の利点は「一つの定義が型と実行時検証の両方を生む」こと。コストは DSL を持ち込むことと、 生成された Schema が実装詳細(たとえば見栄えのしない $ref 構造)を運びうることです。

検証が失敗したとき、特定のフィールドをどう特定しますか?

バリデータが返す instancePath(2020-12)か dataPath(draft-07)を見ます。エラー位置への JSON Pointer です。

[
  {
    "instancePath": "/items/2/price",
    "schemaPath": "#/properties/items/items/properties/price/type",
    "keyword": "type",
    "message": "must be number"
  }
]

instancePath: "/items/2/price" は直接教えます。items 配列の 3 番目の要素の price フィールドの型が誤りです。

実践的な二つの提案:

  1. すべてのエラーを一度に返す。最初の一つで投げるのではなく。フロントエンドが一度にすべて直す方が、往復より速いです。
  2. Pointer を平易な言葉に変えてからエンドユーザーに見せる。/items/2/price は開発者には有用で、ユーザーには無用です。

allErrors: true が多くのバリデータのスイッチです(Ajv は明示的に有効にする必要があります)。既定は fail-fast モードのことが多いです。

additionalProperties: false を有効にすべきですか?

内部契約では有効にし、公開 API では慎重に。

有効にすると、properties で宣言されていないフィールドが検証に失敗します。利点は綴り間違い (userNmae)や予期しないパススルーの検出。欠点は、上流のフィールド追加が下流の消費者を壊し、後方互換性を破壊することです。

妥協:

場面 推奨
内部サービス間 有効。フィールド変更はバージョン管理を通す
公開外部 API 無効。上流のフィールド追加を許す
サードパーティデータの消費 無効。相手側を制御できない
設定検証 有効。設定の綴り間違いは即座に失敗すべき

配列要素が一意であることを検証するには?

二つの異なる意味論を混同しないでください。

{ "uniqueItems": true }

これは要素全体の重複排除で、配列に深く等しい要素が二つないことを要求します。

{
  "type": "array",
  "items": { "type": "object", "properties": { "id": { "type": "string" } } },
  "uniqueItems": true
}

上の断片はオブジェクト全体が重複しないことだけを保証し、{"id":"a","name":"x"} と {"id":"a","name":"y"} は重複に数えません。

特定のフィールドで重複排除することを Schema は表現できず、業務層で検証する必要があります。

const ids = items.map((item) => item.id);
if (new Set(ids).size !== ids.length) {
  throw new Error('id が重複しています');
}

format は実際にメールや日付を検証しますか?

必ずしもそうではありません。仕様では format は既定で「注釈」であり「表明」ではないので、バリデータは検証せずに収集するだけかもしれません。

  • Ajv は別の ajv-formats パッケージをインストールして設定する必要があり、さもないと format: "email" は事実上無効です。
  • 一部のバリデータは format を既定で有効な表明として扱うので、振る舞いが不統一です。
  • 有効にしても email の正規表現は非常に緩く、a@b が通ります。

結論:format を強い検証として扱わないでください。メール、URL、日付の厳格な検証は業務コードで行います。 Schema に format を残してドキュメントとするのは価値がありますが、汚れたデータを止めると期待しないでください。

3. クイックリファレンス

質問 結論
条件でノードを探す JSONPath
正確な位置指定、パッチ適用 JSON Pointer
配列要素を変更 JSON Patch。Merge Patch にはできない
設定のマージ Merge Patch
新規プロジェクトの Schema バージョン 2020-12。prefixItems に注意
Schema から TS 型を生成 json-schema-to-ts + as const
検証エラーの特定 instancePath を読み、allErrors を有効にする
フィールドで重複排除 Schema にはできない。業務層で Set を使う
format: email ほとんどのバリデータは既定で検査しないので当てにしない