過去数年、JSON スキーマの出力に関して多くの質問(およびバグと主張されるもの)を受け取り、かなりの数の議論をしてきました。最も一般的な質問は、「検証に合格したのにエラーが表示されるのはなぜですか?」です。
詳しく見ていきましょう。
前回出力について説明したのは、2019年9月/2020年12月版からの変更を発表するためでした。今回は、読みやすくコンパクトな新しいフォーマットを使用します。
問題なし
出力が混乱する可能性のある箇所に入る前に、ハッピーパス、つまり以下のいずれかのケースについて確認しましょう。
- すべての子ノードが有効なため、全体の検証も有効である場合。
- 1つ以上の子ノードが無効なため、全体の検証が無効である場合。
これらのケースは非常に理解しやすいので、良い出発点となります。

スキーマ
{ "$schema": "https://json-schema.dokyumento.jp/draft/2020-12/schema", "$id": "https://json-schema.dokyumento.jp/blog/interpreting-output/example1", "type": "object", "properties": { "foo": { "type": "boolean" }, "bar": { "type": "integer" } }, "required": [ "foo" ]}
これは非常に基本的なスキーマであり、以下のインスタンスは検証に合格します。
{ "foo": true, "bar": 1 }
出力と共に
{ "valid": true, "evaluationPath": "", "schemaLocation": "https://json-schema.dokyumento.jp/blog/interpreting-output/example1#", "instanceLocation": "", "annotations": { "properties": [ "foo", "bar" ] }, "details": [ { "valid": true, "evaluationPath": "/properties/foo", "schemaLocation": "https://json-schema.dokyumento.jp/blog/interpreting-output/example1#/properties/foo", "instanceLocation": "/foo" }, { "valid": true, "evaluationPath": "/properties/bar", "schemaLocation": "https://json-schema.dokyumento.jp/blog/interpreting-output/example1#/properties/bar", "instanceLocation": "/bar" } ]}
/details
内のサブスキーマの出力ノードはすべて有効であり、ルートも有効で、すべてがうまくいきます。
同様に、これは失敗したインスタンスです(bar
が文字列であるため)。
{ "foo": true, "bar": "value" }
出力と共に
{ "valid": false, "evaluationPath": "", "schemaLocation": "https://json-schema.dokyumento.jp/blog/interpreting-output/example1#", "instanceLocation": "", "details": [ { "valid": true, "evaluationPath": "/properties/foo", "schemaLocation": "https://json-schema.dokyumento.jp/blog/interpreting-output/example1#/properties/foo", "instanceLocation": "/foo" }, { "valid": false, "evaluationPath": "/properties/bar", "schemaLocation": "https://json-schema.dokyumento.jp/blog/interpreting-output/example1#/properties/bar", "instanceLocation": "/bar", "errors": { "type": "Value is \"string\" but should be \"integer\"" } } ]}
/details/1
のサブスキーマ出力は無効であり、ルートも無効であり、失敗したため少し不満かもしれませんが、少なくともその理由がわかります。
それが常に当てはまるのでしょうか?検証に合格したサブスキーマに、失敗したサブスキーマが含まれることはあるのでしょうか?もちろんあります!
より複雑なケース
スキーマとインスタンスを作成して、失敗したノードを出力しながら合格させる方法は無数にあります。ほとんどの場合、複数のオプションを提供するキーワード(anyOf
またはoneOf
)や条件式(if
、then
、else
)に関係しています。これらのケースは、特に、成功した検証結果を生成しながらも*意図的に*失敗するように設計されたサブスキーマを持っています。
この記事では、以下の条件付きスキーマに焦点を当てますが、同じ考え方は「複数のオプション」キーワードを含むスキーマにも当てはまります。

スキーマ
{ "$schema": "https://json-schema.dokyumento.jp/draft/2020-12/schema", "$id": "https://json-schema.dokyumento.jp/blog/interpreting-output/example2", "type": "object", "properties": { "foo": { "type": "boolean" } }, "required": ["foo"], "if": { "properties": { "foo": { "const": true } } }, "then": { "required": ["bar"] }, "else": { "required": ["baz"] }}
このスキーマは、foo
がtrueの場合、bar
プロパティも必要であり、そうでない場合はbaz
プロパティが必要であることを示しています。したがって、次の両方が有効です。
{ "foo": true, "bar": 1 }
{ "foo": false, "baz": 1 }
最初のインスタンスの検証出力を見ると、前のセクションの成功パスの結果に似た出力になります。すべての出力ノードにvalid: true
があり、すべてが理にかなっています。
しかし、2番目のインスタンス(下記)の検証出力を見ると、/if
サブスキーマの出力ノードにvalid: false
があることに気づきます。しかし、全体の検証は合格しています。
{ "valid": true, "evaluationPath": "", "schemaLocation": "https://json-schema.dokyumento.jp/blog/interpreting-output/example2#", "instanceLocation": "", "annotations": { "properties": [ "foo" ] }, "details": [ { "valid": true, "evaluationPath": "/properties/foo", "schemaLocation": "https://json-schema.dokyumento.jp/blog/interpreting-output/example2#/properties/foo", "instanceLocation": "/foo" }, { "valid": false, "evaluationPath": "/if", "schemaLocation": "https://json-schema.dokyumento.jp/blog/interpreting-output/example2#/if", "instanceLocation": "", "details": [ { "valid": false, "evaluationPath": "/if/properties/foo", "schemaLocation": "https://json-schema.dokyumento.jp/blog/interpreting-output/example2#/if/properties/foo", "instanceLocation": "/foo", "errors": { "const": "Expected \"true\"" } } ] }, { "valid": true, "evaluationPath": "/else", "schemaLocation": "https://json-schema.dokyumento.jp/blog/interpreting-output/example2#/else", "instanceLocation": "" } ]}
なぜでしょうか?
出力には理由が含まれる
インスタンスが検証に合格したという単純な結果よりも、特に予想外の結果である場合は、それが検証に合格した*理由*の方が重要です。これをサポートするには、関連するすべての出力ノードを含める必要があります。
失敗した出力ノードを結果から除外すると、
{ "valid": true, "evaluationPath": "", "schemaLocation": "https://json-schema.dokyumento.jp/blog/interpreting-output/example2#", "instanceLocation": "", "annotations": { "properties": [ "foo" ] }, "details": [ { "valid": true, "evaluationPath": "/properties/foo", "schemaLocation": "https://json-schema.dokyumento.jp/blog/interpreting-output/example2#/properties/foo", "instanceLocation": "/foo" }, { "valid": true, "evaluationPath": "/else", "schemaLocation": "https://json-schema.dokyumento.jp/blog/interpreting-output/example2#/else", "instanceLocation": "" } ]}
/else
サブスキーマが評価されたことがわかります。これにより、/if
サブスキーマは必ず失敗したと推測できます。しかし、そのサブスキーマの出力は省略されているため、*なぜ*失敗したのかという情報は得られません。しかし、完全な出力を見ると、foo
がtrueであることを期待していたため、/if
サブスキーマが失敗したことが明らかです。
このため、出力には、評価されたすべてのサブスキーマのノードを保持する必要があります。
また、仕様では、if
キーワードは、全体の検証結果に直接影響を与えないと記載されています。
フォーマットに関する注意
最後に、出力の読み取りにおいて重要な側面がもう1つあります。それはフォーマットです。上記のすべての例では、階層型フォーマット(旧詳細フォーマット)を使用しています。ただし、必要に応じて、リストフォーマット(旧基本フォーマット)を使用することもできます。
単純なスキーマのリストフォーマットによる出力は次のとおりです。
{ "valid": false, "details": [ { "valid": false, "evaluationPath": "", "schemaLocation": "https://json-schema.dokyumento.jp/blog/interpreting-output/example1#", "instanceLocation": "" }, { "valid": true, "evaluationPath": "/properties/foo", "schemaLocation": "https://json-schema.dokyumento.jp/blog/interpreting-output/example1#/properties/foo", "instanceLocation": "/foo" }, { "valid": false, "evaluationPath": "/properties/bar", "schemaLocation": "https://json-schema.dokyumento.jp/blog/interpreting-output/example1#/properties/bar", "instanceLocation": "/bar", "errors": { "type": "Value is \"string\" but should be \"integer\"" } } ]}
すべての出力ノードが単一レベルにあるため、読みやすく処理しやすいです。エラーを見つけるには、/details
内のノードをスキャンして、エラーが含まれているノードを探すだけです。
条件付きスキーマのリストフォーマットによる出力は次のとおりです。
{ "valid": true, "details": [ { "valid": true, "evaluationPath": "", "schemaLocation": "https://json-schema.dokyumento.jp/blog/interpreting-output/example2#", "instanceLocation": "", "annotations": { "properties": [ "foo" ] } }, { "valid": true, "evaluationPath": "/properties/foo", "schemaLocation": "https://json-schema.dokyumento.jp/blog/interpreting-output/example2#/properties/foo", "instanceLocation": "/foo" }, { "valid": false, "evaluationPath": "/if", "schemaLocation": "https://json-schema.dokyumento.jp/blog/interpreting-output/example2#/if", "instanceLocation": "" }, { "valid": true, "evaluationPath": "/else", "schemaLocation": "https://json-schema.dokyumento.jp/blog/interpreting-output/example2#/else", "instanceLocation": "" }, { "valid": false, "evaluationPath": "/if/properties/foo", "schemaLocation": "https://json-schema.dokyumento.jp/blog/interpreting-output/example2#/if/properties/foo", "instanceLocation": "/foo", "errors": { "const": "Expected \"true\"" } } ]}
ここでは、エラーの発生源を考慮する必要があるため、エラーを単にスキャンするだけでは不十分であることが明らかになります。最後の出力ノードのエラーは、/if
サブスキーマのみに関係しており(前述のように)、検証結果には影響しません。
まとめ
JSONスキーマの出力は、検証結果と、評価者がその結果にどのように到達したかを知るために必要なすべての情報を提供します。しかし、それを理解するには、すべての要素が存在する理由を理解する必要があります。
ご質問がありましたら、Slackワークスペース(フッターのリンク)またはディスカッションを開いてお気軽にお問い合わせください。
すべての出力は、オンライン評価ツールhttps://json-everything.net/json-schemaを使用して生成されました。
表紙画像は、Tim Gouw氏によるUnsplash https://unsplash.com/photos/man-wearing-white-top-using-macbook-1K9T5YiZ2WUからのものです。