Dev Study
← 何を作るかを言葉にする
レッスン 8 / 10·約 8 分·第21期 何を作るかを言葉にする

スキーマも、書かなかったことは通す

リクエストがアプリケーションに届くまで
  1. ブラウザURL入力
  2. DNS名前→IP
  3. ルータ経路選択
  4. ロードバランサ振り分け
  5. Nginx受け口
  6. アプリ処理今ここ
  7. DB保存

依頼文を機械が読める条件に書き直すと、曖昧さは消えるのか。アプリの入口で JSON を検査するスキーマで確かめます。

まず結論

このコースの芯は、仕様に書かなかったことは消えずに残り、実装した誰か(人でも AI でも)が黙って決める、でした。機械が確かめる条件にしても同じです。JSON Schema では、書かなかったキーワードは何も拒否しません。

「数量は1〜99の整数・必須、メモは3文字以内」と書いたスキーマは、打ち間違えたキー qtty も admin:true も通しました。書いた「3文字」も、JS・スキーマ・画面の見た目で数え方が違います。

? 読む前に予測

数量 qty を「1〜99の整数・必須」とだけ書いたスキーマに `{"qty":1,"qtty":5,"admin":true}` を渡すと、検証器 ajv 8.20.0 はどう判定したでしょうか。

当てることが目的ではありません。外した予測ほど記憶に残るので、直感で選んでください。

抽象原理

書かなかったキーワードは、偽を出さない

依頼文を「ちゃんと」書いても、読んだ側が黙って選ぶ余地は残ります。では、機械が確かめる条件として書けばどうでしょう。人も AI も解釈せず、検証器が機械的に判定します。

JSON Schema は、届いた JSON がどんな形であるべきかをキーワードで書きます。現行の 2020-12 は json-schema.org が配る Internet-Draft(draft-bhutton-json-schema-01、2022-06-16)です。その Core §7.3 はこう定めます。

A missing keyword MUST NOT produce a false assertion result, MUST NOT produce annotation results, and MUST NOT cause any other schema to be evaluated as part of its own behavioral definition.

書かれていないキーワードは、偽(不合格)を出してはならない。検証は、書いた条件ごとに拒否するかを判定する作業で、書かなかった条件は何も拒否しません。 個別のキーワードも省略時を定めています。

  • additionalProperties(Core §10.3.2.3): Omitting this keyword has the same assertion behavior as an empty schema. 空スキーマは何でも通す。
  • required(Validation §6.5.3): Omitting this keyword has the same behavior as an empty array. 求めるキーが無い。
スキーマは、書いた条件の数だけ拒否する
届いた JSON{"qty":1, "qtty":5, "admin":true}
書いたキーワードごとに確かめる
qty: integer・1〜99書いた → 範囲外を拒否できる
required: ["qty"]書いた → 欠けを拒否できる
additionalProperties書かなかった → 空スキーマと同じ
どれも偽を出さなければ
validqtty と admin は誰も確かめていない
  • ・「書かなかった」は「禁止した」ではなく「何でも可と決めた」と同じ結果になる
  • ・その結果を選んだのは、スキーマを書いた人の沈黙である
具体実物

打ち間違えたキーが、検証を通る

注文の数量 qty と、任意のメモ note を受け取るスキーマです。検証器は ajv 8.20.0 の Ajv2020(npm i ajv@8 で導入。eslint などが間接に入れる ajv 6 系は draft-07 までで、2020-12 用の Ajv2020 を持ちません)。

スキーマ(qty は 1〜99 の整数で必須、note は 3 文字まで)設定ファイル
{
"type": "object",
"properties": {
"qty": { "type": "integer", "minimum": 1, "maximum": 99 },
"note": { "type": "string", "maxLength": 3 }
},
"required": ["qty"]
}
$ node l8-schema.mjs(ajv 8.20.0 / Node v24.21.0。一部の行を抜粋。2回実行して同一)コマンド出力
{"qty":0} invalid: /qty must be >= 1
{"qty":100} invalid: /qty must be <= 99
{"qty":1.5} invalid: /qty must be integer
{"qty":"1"} invalid: /qty must be integer
{"qty":1,"note":"abcd"} invalid: /note must NOT have more than 3 characters
{"qty":1,"qtty":5,"admin":true} valid
{} invalid: must have required property 'qty'

書いた条件はよく働いています。0 も 100 も 1.5 も文字列の "1" も拒否し、{} は required で止めました。それでも6行目は valid です。qtty が qty の打ち間違いだとしても、admin が権限を上げようとする値だとしても、スキーマはそれについて何も書いていません。 受け取ったアプリが admin を読むかどうかは、アプリを書いた誰かが決めます。

additionalProperties: false を足すと結果が変わります。

同じスキーマに additionalProperties: false を足したもの(同じ実行から抜粋)コマンド出力
closed {"qty":1,"qtty":5,"admin":true} invalid: must NOT have additional properties {"additionalProperty":"qtty"}; must NOT have additional properties {"additionalProperty":"admin"}
closed {"qty":1,"note":"ok"} valid

どちらが正しいかは、スキーマの規則からは出てきません。知らないキーを拒否するか通すかは決定で、書かなければ「通す」が選ばれます。

✓ ここまでの確認

`additionalProperties` を書いていないスキーマについて、「スキーマ検証を入れたので、打ち間違えたキーは弾かれる」という理解は妥当ですか。

抽象原理

書いた条件にも、書かなかった読み方が残る

では、書いたキーワードなら意味は1つに決まるのか。maxLength: 3 の「長さ」を Validation §6.3.1 はこう定義します。

The length of a string instance is defined as the number of its characters as defined by RFC 8259 [RFC8259].

参照先の RFC 8259 に character の定義はありません。§1 に A string is a sequence of zero or more Unicode characters [UNICODE]. とあるだけです。単位を言い切れる根拠は Core §4.2.1 の string の定義 A string of Unicode code points, from the JSON "string" value で、コードポイントの数と読むのが Core と整合します。

一方、JS の length は ECMA-262 §6.1.4 が The length of a String is the number of elements (i.e., 16-bit values) within it. と定めます。画面で人が数えるのは、見た目の1文字(書記素)です。依頼文の「3文字以内」は、このどれとも読めます。

具体実物

「3文字」を3通りに数える

$ node l8-units.mjs(maxLength 3、ajv 8.20.0、書記素は Intl.Segmenter(ICU 78.3)。表に整形)表
文字列 UTF-16の単位 コードポイント 書記素 ajv
"abc" 3 3 3 valid
"😀😀😀" 6 3 3 valid
"👨‍👩‍👧" 8 5 1 invalid
"か" + U+3099 2 2 1 valid

😀😀😀 は JS なら6文字で、JS 側で length <= 3 と確かめる実装なら拒否されます。👨‍👩‍👧 は画面では1文字ですが、ajv は拒否しました。実測では ajv の既定はコードポイントで数えており、これは仕様どおりで不具合ではありません。どの数え方が依頼者の意図かは、依頼文に書かれていなければ実装した誰かが選びます。

type: "integer" も同じです。Validation §6.1.1 は integer を any number with a zero fractional part とします。JSON.parse('{"qty":2.0}') を通すと { qty: 2 } になり、ajv は valid と判定しました。「整数」が「小数点を書かない」の意味なら、それはスキーマのどこにもありません。

✓ ここまでの確認

ajv 8.20.0 で `maxLength: 3` に対し、😀😀😀 は valid、見た目1文字の 👨‍👩‍👧 は invalid でした。両方の結果と合う数え方はどれですか。

⇄ 2つを見比べる
具体例 A

JSON Schema の additionalProperties

書かなければ空スキーマと同じで、qtty も admin も通りました。false と宣言したときだけ、挙げなかったキーが拒否されます。

宣言と結果(ajv 8.20.0)表
宣言 {"qty":1,"qtty":5,"admin":true}
additionalProperties なし valid
additionalProperties: false invalid(qtty と admin)
具体例 B

データベースの UNIQUE(第17期を読んだなら)

第17期「一意性は、宣言したときだけ存在する」では、2つの接続で同時に「確かめてから入れる」と、制約を宣言していない表に同じ行が2つ入り、UNIQUE を宣言すると後から来た側が拒否されました。データベースは一つであるべきだと知らされていなければ止めません。

JSON の検証器とデータベース。仕組みはまったく違いますが、共通していることは何でしょうか。

「何を書いたときに、拒否が起きたか」を両方で比べてください。

この章のまとめ

  • JSON Schema 2020-12 Core §7.3: 書かなかったキーワードは偽を出さない。 スキーマは書いた条件の分だけ拒否する。
  • additionalProperties の省略は空スキーマ、required の省略は空配列。打ち間違えたキーも、書かなければ通る。
  • maxLength の単位は Core の定義からコードポイント。JS の length(UTF-16 の単位)とも、画面の見た目とも違う。
  • integer は「小数部がゼロの数」。JSON テキストの 2.0 は通る。
  • データベースの UNIQUE と同じく、機械が確かめる条件も、宣言した分しか守らない。 書かなかった分は、実装した誰かが黙って決める。

自分の現場で確かめる

読んだだけでは身につきません。手元の環境で実際に見てみてください。

環境:

理解度チェック

読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。

問題 1 / 3正解: 0

properties に qty を書き、`required` を書かなかったスキーマに `{}` を渡すと、2020-12 の Validation 文書に照らしてどうなりますか。

用語集(5語)
JSON Schema 2020-12
JSON の形を条件として書く語彙。json-schema.org が配る現行版は Internet-Draft(draft-bhutton-json-schema-01 ほか、2022-06-16)で、RFC ではない。
additionalProperties
properties 等に挙げなかったキーの値に掛ける条件。省略すると空スキーマと同じで何でも通る。false にすると、挙げなかったキーはすべて拒否される。
コードポイント
Unicode が文字に割り当てた番号。😀 は1つ、👨‍👩‍👧 は5つ(3人の絵文字と、つなぐ記号2つ)。
書記素
人が1文字と見る単位。「か」に結合用の濁点 U+3099 を続けた並びは、コードポイント2つで書記素1つ。
UTF-16 の単位
JS の length が数える16ビットの値。😀 は2単位なので `"😀😀😀".length` は6。