データ形式・構造化データ
必須項目の抜けや型の間違いを見つけたい
使うツール
括弧も引用符も正しいのに、データが受け付けられない。JSONの構文確認は『読める形か』を調べますが、countが数値か、nameが存在するかまでは、JSONそのものの規則からは決まりません。その約束をデータとは別に書くのがSchemaです。今回は外部APIへ送らず、手元の小さな例で違いを確かめます。
SheepDevのJSON Schema検証は、対応キーワードを絞った検証です。公式規格のあらゆるSchemaを実行できるものではありません。記事では現在の実装で確認できる必須項目、単一の型、列挙値、数値範囲を使います。業務の契約全体を保証する代わりではなく、入力がどの条件で違っているかを見つける補助として使います。
まず、正しいデータとSchemaを一組用意する
{"name":"Notebook","count":2,"status":"draft"}{
"type": "object",
"required": ["name", "count"],
"properties": {
"name": { "type": "string" },
"count": { "type": "integer", "minimum": 0 },
"status": { "enum": ["draft", "published"] }
}
}この組合せは検証を通ります。nameとcountが存在し、nameは文字列、countは0以上の整数、statusは許された二つの値の一方です。最初に成功する組を用意するのは、データ側とSchema側のどちらを間違えたかを切り分けるためです。以降はSchemaを固定し、データの一条件だけを変えます。
propertiesに書いただけでは、必須にならない
{"name":"Notebook","status":"draft"}countはrequiredに含まれるため、この入力は失敗します。一方、statusを削除しただけなら、今回のSchemaでは成功します。propertiesは、その項目が存在するときの条件を記述する場所で、requiredは存在が必要な項目を列挙する場所です。項目を定義したつもりなのに欠落を検出できない場合は、この二つを確認してください。
必須という言葉も、『空でない値が入っている』と同じではありません。nameを空文字へ変えても、今回の条件は文字列であることだけなので通ります。空文字を禁止する規則が必要なら、その検証を別に用意する必要があります。この例の成功を、業務上使える名前が入力された保証と読み替えないでください。
2と"2"とnullは、別の値
{"name":"Notebook","count":"2","status":"draft"}引用符で囲んだ2は文字列なので、integerの条件に合いません。検証は『数値に直せそうだから許可する』とは判断しません。nullへ変えた場合も、項目自体は存在しますが整数ではないので失敗します。欠落、空文字、null、数値の0を同じ『未入力』として混ぜると、受け渡しの約束が曖昧になります。
CSVから変換したデータでこの問題が起きることがあります。CSVの文字列をJSONへ移しただけでは、2という見た目のセルが自動的に整数になるとは限りません。先頭ゼロを持つIDなら、むしろ文字列で保持する方が必要な場合もあります。Schemaを通すためだけにすべて数値へ変えるのではなく、項目ごとの意味を確認します。
型が合っていても、許される値とは限らない
{"name":"Notebook","count":-1,"status":"archived"}-1は整数ですがminimum:0を満たさず、archivedはenumにないため失敗します。型だけでは業務の値域を表せないことが分かります。countを0、statusをdraftへ戻せば、この二つの問題は解消します。複数のエラーがある場合は、一つ直した結果で残りも消えると決め付けず、再検証して残った条件を確認します。
enumへ値を追加すれば検証は通るかもしれませんが、受け取り側がその状態を扱えるとは限りません。エラーをなくすために契約を緩めるのではなく、データが誤っているのか、正式な仕様が変わったのかを確認します。Schemaも仕様の一部なので、利用する側と作る側が別々の版を使わないよう管理が必要です。
未知の項目まで禁止した検証ではない
今回のデータへextraという項目を足しても、このSchemaはそれを禁止していません。propertiesに列挙したものだけが存在してよい、という意味ではないからです。また、SheepDevではadditionalPropertiesなど未対応のキーワードはエラーになります。完全なSchemaが必要な場合は、目的の版とキーワードへ対応した検証器で別途確認します。
$schema、$ref、$dynamicRefを使うSchemaも、このツールでは受け付けず外部取得しません。公式のSchema例をコピーしただけで動かないことがありますが、未対応部分を消して通った結果を、元のSchema全体で検証した結果として扱わないでください。切り出した小さな条件の確認と、正式な契約全体の確認を分けて記録します。
成功した理由と、まだ確かめていない条件を残す
検証が成功したら、必須項目、型、列挙値、数値範囲のどこまで確認したかを残します。通信先の認証、IDの実在、在庫、他項目との組合せ規則は、この例では調べていません。『JSON Schemaで成功』を『APIが必ず受け付ける』へ言い換えないことが大切です。APIへ送る前の一段階として使います。
テストには正常例だけでなく、項目の欠落、文字列化した数値、null、負数、未知の状態を残します。許可すべき入力が通ることと、拒否すべき入力が落ちることの両方が確認できれば、Schemaを変更した際にも意図しない緩和を見つけやすくなります。エラー文の位置を手掛かりに、データと契約のどちらを直すべきか判断してください。
Schemaが通ったのに、注文できないのはなぜ?
数量が整数で1以上、商品IDが文字列、という検証を通っても、その商品が販売中か、在庫が足りるかまでは分かりません。Schemaで確かめた入力の形と、業務データに照らして確かめる条件は別です。このツールは外部サービスやデータベースへ問い合わせないので、実在するIDかどうかを成功表示から読み取ることはできません。『入力形式の条件を満たした』という結果を、『注文可能なデータである』へ広げず、どの層の検証が終わったかを明確にします。
検証条件を直すときは、正常例だけでなく『必須項目がない』『数値を文字列にした』『列挙した値以外を入れた』という失敗例を残します。Schemaの変更後、以前は拒否した例が通るようになったなら、条件を意図して緩めたのかを確認できます。エラーを消すためにrequiredを外すと、問題が解決したのではなく検査対象が減っただけかもしれません。使うキーワードがこの実装の対応範囲にあるかも確認し、未対応の条件を無視して検証したことにしないようにします。