RFC 8927 JSON 型定義 2020年11月
Carion 実験的 [ページ]
ストリーム:
独立投稿
RFC:
8927
カテゴリ:
実験的
公開日:
ISSN:
2070-1721
著者:
U. Carion
Segment

RFC 8927

JSON 型定義

概要

本文書は、JavaScript Object Notation (JSON) メッセージの形状を記述するための、JSON Type Definition (JTD) と呼ばれる形式を提案する。その主な目標は、スキーマからの コード生成と、標準化されたエラー指示子による移植可能な検証を 可能にすることである。この目的のため、JTD は意図的に、 主流のプログラミング言語の型システムより表現力が高くならないよう 制限されている。この意図的な制限と、JTD スキーマを JSON 文書にするという決定により、JTD 上のツール構築が容易になる。

本文書は IETF の合意を得たものではなく、JTD の概念に関する 実験を促進するためにここに提示されている。

このメモの位置づけ

本文書は Internet Standards Track 仕様ではなく、検討、 実験的実装、および評価のために公開される。

本文書は、インターネットコミュニティ向けの Experimental Protocol を 定義する。これは、他の RFC ストリームとは独立した RFC Series への寄稿である。RFC Editor はその裁量により本文書を 公開することを選択しており、実装または展開に対する価値について いかなる声明も行わない。RFC Editor によって公開が承認された文書は、 どの水準の Internet Standard の候補でもない。RFC 7841 の Section 2 を参照。

本文書の現在の状態、正誤表、およびフィードバックの送付方法に関する 情報は、 https://www.rfc-editor.org/info/rfc8927 で入手できる。

目次

1. 導入

本文書は、JSON Type Definition (JTD) と呼ばれる、JSON [RFC8259] のためのスキーマ言語について記述する。

JSON データを記述するための選択肢は多く存在する。JTD の領域は、 スキーマからのコード生成を可能にすることに重点を置く点にある。この目的のため、 JTD の表現力は、主流のプログラミング言語の型システムで表現できるものより 強力にならないよう、意図的に制限されている。

JTD の目標は次のとおりである。

JTD は、かなり最小限のスキーマ言語として意図的に設計されている。 したがって、JTD は JSON のいくつかのカテゴリを記述できるものの、 自身の構造を記述することはできない。本文書では、JTD の構文を記述するために Concise Data Definition Language (CDDL) [RFC8610] を使用する。スキーマ言語の表現力を最小限に保つことで、JTD は コード生成と標準化されたエラー指示子を実装しやすくする。

本文書の例では、C++ プログラミング言語の構成要素を使用する。 これらの例は、読者が JTD の原則を理解する助けとして提供されるものであり、 いかなる意味でも限定的なものではない。

JTD の機能セットは、JSON を使用するアプリケーションにおける 一般的なパターンを表現しつつ、広く使用されているプログラミング言語と 明確に対応するよう設計されている。したがって、JTD は次をサポートする。

JSON における一般的なパターンという原則により、JTD は 64 ビット整数をサポートしない。これは、それらが通常、相互運用できない 方法(すなわち、Section 2.2 of [RFC7493] の推奨を無視する方法)または 相互に一貫しない方法で JSON 上で送信されるためである。Appendix A.1 は、 JTD が 64 ビット整数をサポートしない理由をさらに詳しく述べている。

一般的なプログラミング言語との明確な対応という原則により、 JTD は、たとえば 2**53-1 までの整数のためのデータ型をサポートしない。

多くのユースケースにおいて、JTD の表現力を持つスキーマ言語で 十分であることが期待される。より表現力の高い言語が必要な場合には、 CDDL などの代替手段が存在する。

本文書は IETF の合意を得たものではなく、JTD の概念に関する 実験を促進するためにここに提示されている。この実験の目的は、 JTD の経験を得て、この作業をそれに応じて改訂する可能性を探ることである。 JTD が価値ある人気のある手法であると判断された場合、さらなる議論と 改訂のために IETF に持ち込まれる可能性がある。

本文書は次の構成を持つ。 Section 2 は JTD の構文を定義する。 Section 3 は JTD の意味論を記述する。 これには、あるデータがスキーマを満たすかどうかの判定、および データが不適合である場合に生成すべきエラー指示子が含まれる。 Appendix A は、 JTD から特定の機能が省略されている理由を論じる。Appendix B は、さまざまな JTD スキーマとそれらに対応する CDDL を示す。

1.1. 用語

本文書におけるキーワード "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" は、 ここに示されるようにすべて大文字で現れる場合に限り、 BCP 14 [RFC2119] [RFC8174] に記述されているとおりに 解釈されるものとする。

本文書に "JSON Pointer" という用語が現れる場合、 それは [RFC6901] で定義されているとおりに 理解されるものとする。

本文書における "object"、"member"、"array"、"number"、"name"、 および "string" という用語は、[RFC8259] に記述されているとおりに解釈されるものとする。

本文書に "instance" という用語が現れる場合、それは JTD スキーマに対して検証される JSON 値を指す。この値は JSON 文書全体であってもよく、JSON 文書内に埋め込まれた値であってもよい。

1.2. 実験の範囲

JTD は実験である。この実験への参加は、 交換される JSON メッセージを検証または文書化するために JTD を使用すること、 または JTD 上にツールを構築することから成る。この実験の結果に関する フィードバックは、著者へ電子メールで送ることができる。この実験の参加者は、 主に JSON ベースの API を提供または消費するノードであることが想定される。

ノードは、JTD スキーマに対して JSON メッセージを検証している場合、 または別のノードがそうすることに依存している場合、自分が実験に参加していることを 知る。ノードは、JTD スキーマから生成されたコードを実行している場合にも、 実験に参加している。

この実験が「外へ漏れ出す」リスクは、JTD をサポートするノードが、 そのようなサポートを持たない別のノードに対し、何らかの JTD スキーマに対して メッセージを検証することを期待する、という形を取る。そのような場合、 結果としてノード同士が情報を正しく交換できなくなる可能性が高い。

この実験は、JTD が複数の独立した当事者によって実装され、 それらの当事者が、内部システム内または独立した当事者が運用するシステム間での 情報交換を促進するために JTD を成功裏に使用したとき、成功とみなされる。

この実験が成功とみなされ、JTD が価値ある人気のある手法であると 判断された場合、さらなる議論と改訂のために IETF に持ち込まれる可能性がある。 この議論と改訂の 1 つの可能な結果として、ワーキンググループが JTD の Standards Track 仕様を作成することが考えられる。

JTD のいくつかの実装、および JTD に関連するコード生成器や その他のツールは、 <https://github.com/jsontypedef> で入手できる。

2. 構文

本節は、JSON 文書が正しい JTD スキーマであるのは どのような場合かを記述する。Concise Data Definition Language (CDDL) は、 JTD スキーマのような複雑な JSON 形式を定義する作業に適しているため、 本節では CDDL を用いて JTD スキーマの形式を記述する。

JTD スキーマは、再帰的に他のスキーマを含むことができる。本文書において、 "root schema" とは、他のスキーマ内に含まれていないスキーマ、 すなわち "top level" のスキーマである。

JTD スキーマは、適切な形式を取る JSON オブジェクトである。JTD スキーマは、Section 2.3 で述べる "additional data" を含むことができる。ルート JTD スキーマは、 任意で定義(名前からスキーマへの写像)を含むことができる。

正しいルート JTD スキーマは、本節で記述される "root-schema" CDDL 規則に一致しなければならない(MUST)。正しい非ルート JTD スキーマは、本節で記述される "schema" CDDL 規則に一致しなければならない(MUST)。

; root-schema is identical to schema, but additionally allows for
; definitions.
;
; definitions are prohibited from appearing on non-root schemas.
root-schema = {
  ? definitions: { * tstr => { schema}},
  schema,
}
; schema is the main CDDL rule defining a JTD schema.
;
; All JTD schemas are JSON objects taking on one of eight forms
; listed here.
schema = (
  ref //
  type //
  enum //
  elements //
  properties //
  values //
  discriminator //
  empty //
)
; shared is a CDDL rule containing properties that all eight schema
; forms share.
shared = (
  ? metadata: { * tstr => any },
  ? nullable: bool,
)
; empty describes the "empty" schema form.
empty = shared
; ref describes the "ref" schema form.
;
; There are additional constraints on this form that cannot be
; expressed in CDDL. Section 2.2.2 describes these additional
; constraints in detail.
ref = ( ref: tstr, shared )
; type describes the "type" schema form.
type = (
  type: "boolean"
    / "float32"
    / "float64"
    / "int8"
    / "uint8"
    / "int16"
    / "uint16"
    / "int32"
    / "uint32"
    / "string"
    / "timestamp",
  shared,
)
; enum describes the "enum" schema form.
;
; There are additional constraints on this form that cannot be
; expressed in CDDL. Section 2.2.4 describes these additional
; constraints in detail.
enum = ( enum: [+ tstr], shared )
; elements describes the "elements" schema form.
elements = ( elements: { schema }, shared )
; properties describes the "properties" schema form.
;
; This CDDL rule is defined so that a schema of the "properties" form
; may omit a member named "properties" or a member named
; "optionalProperties", but not both.
;
; There are additional constraints on this form that cannot be
; expressed in CDDL. Section 2.2.6 describes these additional
; constraints in detail.
properties = (with-properties // with-optional-properties)
with-properties = (
  properties: { * tstr => { schema }},
  ? optionalProperties: { * tstr => { schema }},
  ? additionalProperties: bool,
  shared,
)
with-optional-properties = (
  ? properties: { * tstr => { schema }},
  optionalProperties: { * tstr => { schema }},
  ? additionalProperties: bool,
  shared,
)
; values describes the "values" schema form.
values = ( values: { schema }, shared )
; discriminator describes the "discriminator" schema form.
;
; There are additional constraints on this form that cannot be
; expressed in CDDL. Section 2.2.8 describes these additional
; constraints in detail.
discriminator = (
  discriminator: tstr,
  ; Note well: this rule is defined in terms of the "properties"
  ; CDDL rule, not the "schema" CDDL rule.
  mapping: { * tstr => { properties } }
  shared,
)
図 1: スキーマの CDDL 定義

本節の残りでは、CDDL では表現できない JTD スキーマに対する制約を記述する。また、有効な JTD スキーマと 無効な JTD スキーマの例も示す。

2.1. ルートスキーマと非ルート スキーマ

図 1 の "root-schema" 規則は、 "definitions" という名前のメンバーを許すが、 "schema" 規則はそのようなメンバーを許さない。これは、 ルート(すなわち "top-level")JTD スキーマだけが "definitions" オブジェクトを持つことができ、サブスキーマは持てないことを意味する。

したがって、

   { "definitions": {} }

は正しい JTD スキーマであるが、

   {
     "definitions": {
       "foo": {
         "definitions": {}
       }
     }
   }

はそうではない。なぜなら、サブスキーマ(たとえば "/definitions/foo" にあるオブジェクト)は、"definitions" という名前の メンバーを持ってはならないからである。

2.2. 形式

JTD スキーマ(すなわち、図 1 の "schema" CDDL 規則を満たす JSON オブジェクト)は、 8 つの形式のうち 1 つを取らなければならない。これらの形式は相互に 排他的になるように定義されており、1 つのスキーマが同時に複数の形式を 満たすことはできない。

2.2.1.

"empty" 形式は、図 1 の "empty" CDDL 規則によって定義される。"empty" 形式の意味論は Section 3.3.1 で記述される。

"empty" という名前にもかかわらず、"empty" 形式のスキーマは、 必ずしも空の JSON オブジェクトではない。8 つの形式のいずれのスキーマと同様に、 "empty" 形式のスキーマは、"nullable"(その値は "true" または "false" でなければならない)または "metadata"(その値はオブジェクトで なければならない)という名前のメンバー、またはその両方を含むことができる。

したがって、

   {}

および

   { "nullable": true }

および

   { "nullable": true, "metadata": { "foo": "bar" }}

は "empty" 形式の正しい JTD スキーマであるが、

   { "nullable": "foo" }

はそうではない。なぜなら、"nullable" という名前のメンバーの値は "true" または "false" でなければならないからである。

2.2.2. 参照

"ref" 形式は、図 1 の "ref" CDDL 規則で 定義される。"ref" 形式の意味論は Section 3.3.2 で記述される。

"ref" 形式のスキーマが正しいためには、 "ref" という名前のメンバーの値が、そのスキーマが現れるスキーマの ルートレベルにある定義のいずれかを参照しなければならない。より形式的には、 "ref" 形式のスキーマ S について、次のとおりである。

  • B を、そのスキーマを含む ルートスキーマ、またはそれ自体がルートスキーマである場合は そのスキーマ自身とする。
  • R を、S の "ref" という名前のメンバーの値とする。

スキーマが正しい場合、B は "definitions" という名前のメンバー D を持たなければならず(MUST)、 DR に等しい名前のメンバーを含まなければならない(MUST)。

したがって、

   {
     "definitions": {
       "coordinates": {
         "properties": {
           "lat": { "type": "float32" },
           "lng": { "type": "float32" }
         }
       }
     },
     "properties": {
       "user_location": { "ref": "coordinates" },
       "server_location": { "ref": "coordinates" }
     }
   }

は正しい JTD スキーマであり、 同じものを 2 度再定義することを避ける、という "ref" 形式の意義を示している。 しかし、

   { "ref": "foo" }

は正しい JTD スキーマではない。最上位の "definitions" が存在しないため、"ref" 形式は 正しくなり得ない。同様に、

   { "definitions": { "foo": {}}, "ref": "bar" }

も正しい JTD スキーマではない。最上位の "definitions" に "bar" という名前のメンバーが存在しないためである。

2.2.3.

"type" 形式は、図 1 の "type" CDDL 規則で 定義される。"type" 形式の意味論は Section 3.3.3 で記述される。

"type" 形式の正しい JTD スキーマの例として、

   { "type": "uint8" }

は正しい JTD スキーマである。一方、

   { "type": true }

および

   { "type": "foo" }

は正しいスキーマではない。なぜなら、"true" も JSON 文字列 "foo" も、図 1 の "type" CDDL 規則で 記述される "type" メンバーの許可値のリストに含まれていないからである。

2.2.4. 列挙

"enum" 形式は、図 1 の "enum" CDDL 規則で 定義される。"enum" 形式の意味論は Section 3.3.4 で記述される。

"enum" 形式のスキーマが正しいためには、 "enum" という名前のメンバーの値は、空でない文字列の配列でなければならず、 その配列は重複値を含んではならない。より形式的には、 "enum" 形式のスキーマ S について、次のとおりである。

  • E を、 S の "enum" という名前のメンバーの値とする。

スキーマが正しい場合、E の要素のうち、 等しい文字列値をエンコードするような任意の組は存在してはならない(MUST NOT)。 ここで、文字列の等価性は Section 8.3 of [RFC8259] における定義に従う。

したがって、

   { "enum": [] }

は正しい JTD スキーマではない。"enum" という名前のメンバーの値は 空であってはならないためである。また、

   { "enum": ["a\\b", "a\u005Cb"] }

も正しい JTD スキーマではない。なぜなら、

   "a\\b"

および

   "a\u005Cb"

は、Section 8.3 of [RFC8259] における文字列等価性の定義により、 等しい文字列をエンコードするためである。対照的に、

   { "enum": ["PENDING", "IN_PROGRESS", "DONE" ]}

は "enum" 形式の正しい JTD スキーマの例である。

2.2.5. 要素

"elements" 形式は、図 1 の "elements" CDDL 規則で定義される。"elements" 形式の 意味論は Section 3.3.5 で記述される。

"elements" 形式の正しい JTD スキーマの例として、

   { "elements": { "type": "uint8" }}

は正しい JTD スキーマである。一方、

   { "elements": true }

および

   { "elements": { "type": "foo" } }

は正しいスキーマではない。なぜなら、

   true

   { "type": "foo" }

も正しい JTD スキーマではなく、"elements" という名前のメンバーの値は 正しい JTD スキーマでなければならないためである。

2.2.6. プロパティ

"properties" 形式は、図 1 の "properties" CDDL 規則で定義される。"properties" 形式の 意味論は Section 3.3.6 で記述される。

"properties" 形式のスキーマが正しいためには、 プロパティは必須(すなわち "properties" 内)または任意 (すなわち "optionalProperties" 内)のどちらかでなければならず、 両方であってはならない。

より形式的には、あるスキーマが "properties" という名前のメンバー (値を P とする)と、"optionalProperties" という名前の別のメンバー (値を O とする)の両方を持つ場合、OP は 共通するメンバー名を持ってはならない(MUST NOT)。 すなわち、Section 8.3 of [RFC8259] における文字列等価性の定義の下で、 P のいずれのメンバーも、O のいずれのメンバーの名前とも 等しい名前を持ってはならない。

したがって、

   {
     "properties": { "confusing": {} },
     "optionalProperties": { "confusing": {} }
   }

は正しい JTD スキーマではない。"confusing" が "properties" と "optionalProperties" の両方に現れるためである。 対照的に、

   {
     "properties": {
       "users": {
         "elements": {
           "properties": {
             "id": { "type": "string" },
             "name": { "type": "string" },
             "create_time": { "type": "timestamp" }
           },
           "optionalProperties": {
             "delete_time": { "type": "timestamp" }
           }
         }
       },
       "next_page_token": { "type": "string" }
     }
   }

は "properties" 形式の正しい JTD スキーマであり、 ページ分けされたユーザー一覧を記述し、JTD スキーマの構文の 再帰的性質を示している。

2.2.7.

"values" 形式は、図 1 の "values" CDDL 規則で定義される。"values" 形式の意味論は Section 3.3.7 で記述される。

"values" 形式の正しい JTD スキーマの例として、

   { "values": { "type": "uint8" }}

は正しい JTD スキーマである。一方、

   { "values": true }

および

   { "values": { "type": "foo" } }

は正しいスキーマではない。なぜなら、

   true

   { "type": "foo" }

も正しい JTD スキーマではなく、"values" という名前のメンバーの値は 正しい JTD スキーマでなければならないためである。

2.2.8. 判別子

"discriminator" 形式は、図 1 の "discriminator" CDDL 規則で定義される。"discriminator" 形式の 意味論は Section 3.3.8 で記述される。 "discriminator" 形式の意味論を理解することは、 本節が 図 1 におけるものを超える "discriminator" 形式の制約を提供する理由を理解するうえで、 読者の助けになるだろう。

タグ付き union の "discriminator" プロパティに対する 曖昧または満たせない制約を防ぐため、"discriminator" 形式のスキーマには 追加の制約が存在する。"discriminator" 形式のスキーマについて、次のとおりである。

  • D を、 "discriminator" という名前を持つスキーマのメンバーとする。
  • M を、 "mapping" という名前を持つスキーマのメンバーとする。

スキーマが正しい場合、M のすべてのメンバー値 S は、 "properties" 形式のスキーマである。それぞれの S について、次のとおりである。

  • S が、 名前が "nullable" に等しいメンバー N を持つ場合、 N の値は JSON プリミティブ値 "true" であってはならない(MUST NOT)。
  • S の各メンバー P のうち、 名前が "properties" または "optionalProperties" に等しいものについて、 P の値はオブジェクトでなければならず、D の値に等しい名前の メンバーを含んではならない(MUST NOT)。

したがって、

   {
     "discriminator": "event_type",
     "mapping": {
       "can_the_object_be_null_or_not?": {
         "nullable": true,
         "properties": { "foo": { "type": "string" } }}
       }
     }
   }

は不正なスキーマである。"mapping" のメンバーが、 値が "true" である "nullable" という名前のメンバーを持つためである。 これは、インスタンスが null であり得ることを示唆する。しかし、最上位の スキーマには "true" に設定されたそのような "nullable" がなく、 これは実際にはインスタンスが null になれないことを示唆する。 もしこれが正しい JTD スキーマであったなら、どの情報が優先されるのか 不明確になる。

JTD は、"discriminator" 形式のスキーマによって記述される インスタンスが null であり得るかどうかについて、矛盾する指定の可能性を 構文レベルで禁止することにより、そのような曖昧さを扱う。 discriminator の "mapping" 内のスキーマは、"nullable" を "true" に設定できない。この方法で "nullable" を使用できるのは discriminator 自体だけである。

また、次のことも従う。

   {
     "discriminator": "event_type",
     "mapping": {
       "is_event_type_a_string_or_a_float32?": {
         "properties": { "event_type": { "type": "float32" }}
       }
     }
   }

および

   {
     "discriminator": "event_type",
     "mapping": {
       "is_event_type_a_string_or_an_optional_float32?": {
         "optionalProperties": { "event_type": { "type": "float32" }}
       }
     }
   }

は不正なスキーマである。"event_type" が "discriminator" の値であると同時に、"mapping" メンバーの "properties" または "optionalProperties" のいずれかにおける メンバー名でもあるためである。これは曖昧である。通常、 "discriminator" キーワードは "event_type" が文字列であることを期待する ことを示すが、スキーマの別の部分は "event_type" が数値であることを 期待すると指定しているからである。

JTD は、discriminator の "tags" について矛盾する指定の可能性を 構文レベルで禁止することにより、そのような曖昧さを扱う。 Discriminator の "tags" は、スキーマの他の部分で再定義できない。

対照的に、

   {
     "discriminator": "event_type",
     "mapping": {
       "account_deleted": {
         "properties": {
           "account_id": { "type": "string" }
         }
       },
       "account_payment_plan_changed": {
         "properties": {
           "account_id": { "type": "string" },
           "payment_plan": { "enum": ["FREE", "PAID"] }
         },
         "optionalProperties": {
           "upgraded_by": { "type": "string" }
         }
       }
     }
   }

は正しいスキーマであり、JSON ベースのメッセージングシステムで 一般的なデータのパターンを記述している。Section 3.3.8 は、 このスキーマが受け入れるものと拒否するものの例を提供する。

2.3. JTD の構文の拡張

本文書は、Section 3 で記述される JTD スキーマ検証のための拡張メカニズムを記述しない。 しかし、スキーマは任意で "metadata" キーワードを含むように定義されており、 その値は任意の JSON オブジェクトである。このオブジェクトのメンバーを "metadata members" と呼ぶ。

ユーザーは、検証に関係しない情報を伝えるために、 JTD スキーマへ metadata members を追加してよい(MAY)。 たとえば、そのような metadata members は、コード生成器へのヒントを提供したり、 スキーマからユーザーインターフェイスを生成するライブラリの特別な動作を トリガーしたりすることができる。

ユーザーは、metadata members が他の当事者に理解されることを 期待すべきではない(SHOULD NOT)。 その結果、他の当事者との一貫した検証が要件である場合、ユーザーは Section 3 で記述されるスキーマ検証の動作に 影響を与えるために metadata members を使用してはならない(MUST NOT)。

ユーザーは、これらの他の当事者が何らかの形でその metadata members を サポートすることが分かっている場合、metadata members が他の当事者に 理解されることを期待してよく(MAY)、また metadata members を用いて スキーマ検証の動作に影響を与えてよい(MAY)。 たとえば、2 つの当事者は、検証に影響するカスタム metadata member を持つ 拡張 JTD をサポートすることに、帯域外で合意してもよい。

3. 意味論

本節は、あるインスタンスが正しい JTD スキーマに対して 有効であるのはどのような場合か、およびインスタンスが無効である場合に 生成すべきエラー指示子について記述する。

3.1. 追加プロパティの 許可

ユーザーは、インスタンス内の "unspecified" メンバーに関して、異なる望ましい動作を持つことがある。 たとえば、図 2 の JTD スキーマを考える。

{ "properties": { "a": { "type": "string" }}}
図 2: 説明用の JTD スキーマ

一部のユーザーは、次のものが

   {"a": "foo", "b": "bar"}

図 2 のスキーマを満たすと期待するかもしれない。 他のユーザーは同意しないかもしれない。なぜなら "b" は スキーマで記述されるプロパティの 1 つではないからである。本文書では、 この例の "b" のような "unspecified" メンバーを許可することは、 評価が "allow additional properties" モードにある場合に発生する。

スキーマの評価は既定では追加プロパティを許可しないが、 スキーマが "additionalProperties" という名前のメンバーを含み、 そのメンバーの値が "true" である場合、これを上書きできる。

より形式的には、スキーマ S の評価は、 S のメンバーのうち、名前が "additionalProperties" に等しく、 値がブール値 "true" であるものが存在する場合に、 "allow additional properties" モードにある。それ以外の場合、 S の評価は "allow additional properties" モードではない。

未知のプロパティを許可することがスキーマ評価にどのように影響するかについては Section 3.3.6 を参照。簡単に言えば、 次のスキーマは

   { "properties": { "a": { "type": "string" }}}

次を拒否する。

   { "a": "foo", "b": "bar" }

しかし、次のスキーマは

   {
     "additionalProperties": true,
     "properties": { "a": { "type": "string" }}
   }

次を受け入れる。

   { "a": "foo", "b": "bar" }

"additionalProperties" はサブスキーマに "継承" されないことに注意。たとえば、次の JTD スキーマは

   {
     "additionalProperties": true,
     "properties": {
       "a": {
         "properties": {
           "b": { "type": "string" }
         }
       }
     }
   }

次を受け入れるが、

   { "a": { "b": "c" }, "foo": "bar" }

次を拒否する。

   { "a": { "b": "c", "foo": "bar" }}

これは、ルートレベルの "additionalProperties" が サブスキーマの動作に影響しないためである。

図 1 から分かるように、 "properties" 形式のスキーマだけが "additionalProperties" という名前の メンバーを持てることに注意。

3.2. エラー

一貫した検証エラー処理を容易にするため、 本文書は標準的なエラー指示子形式を規定する。実装は、この標準形式で エラー指示子を生成することをサポートすべきである(SHOULD)。

標準的なエラー指示子形式は JSON 配列である。この配列の要素の 順序は規定されない。この配列の要素は、次を持つ JSON オブジェクトである。

  • "instancePath" という名前のメンバー。その値は、 JSON Pointer をエンコードする JSON 文字列である。この JSON Pointer は、 拒否されたインスタンスの部分を指す。
  • "schemaPath" という名前のメンバー。その値は、 JSON Pointer をエンコードする JSON 文字列である。この JSON Pointer は、 インスタンスを拒否したスキーマの部分を指す。

"instancePath" と "schemaPath" の値は スキーマの形式に依存し、Section 3.3 で詳細に記述される。

3.3. 形式

本節は、8 つの JTD スキーマ形式それぞれについて、 インスタンスが受け入れられるかどうかを定める規則と、 インスタンスが無効である場合に生成すべきエラー指示子を記述する。

正しいスキーマが取り得る形式は、 Section 2 で形式的に記述されている。

3.3.1.

"empty" 形式は、値が不明、予測不能、またはその他の形で スキーマによって制約されないインスタンスを記述することを意図している。 "empty" 形式の構文は Section 2.2.1 で記述される。

スキーマが "empty" 形式である場合、それはすべての インスタンスを受け入れる。"empty" 形式のスキーマは、いかなる エラー指示子も生成しない。

3.3.2. 参照

"ref" 形式は、スキーマがルートスキーマの "definitions" 内にある何かに基づいて定義される場合のためのものである。 "ref" 形式により、スキーマの繰り返しを減らすことができ、また 再帰構造を記述することも可能になる。"ref" 形式の構文は Section 2.2.2 で記述される。

スキーマが "ref" 形式である場合、次のとおりである。

  • スキーマが "nullable" という名前のメンバーを持ち、 その値がブール値 "true" であり、インスタンスが JSON プリミティブ値 "null" である場合、そのスキーマは インスタンスを受け入れる。

    それ以外の場合、次のとおりである。

    • R を、 "ref" という名前を持つスキーマメンバーの値とする。
    • B を、 そのスキーマを含むルートスキーマ、またはそれ自体がルートスキーマである場合は そのスキーマ自身とする。
    • D を、 B の "definitions" という名前のメンバーとする。Section 2 により、 D が存在することは分かっている。
    • S を、 D のメンバーのうち、その名前が R に等しいものの 値とする。Section 2.2.2 により、S が存在し、スキーマであることは分かっている。

S がインスタンスを受け入れる場合に限り、 そのスキーマはインスタンスを受け入れる。そうでない場合、この場合に返す エラー指示子は、S をインスタンスに対して評価して得られる エラー指示子の和集合である。

たとえば、次のスキーマは

   {
     "definitions": { "a": { "type": "float32" }},
     "ref": "a"
   }

次を受け入れるが、

   123

次を拒否する。

   null

エラー指示子は次のとおりである。

   [{ "instancePath": "", "schemaPath": "/definitions/a/type" }]

次のスキーマは

   {
     "definitions": { "a": { "type": "float32" }},
     "ref": "a",
     "nullable": true
   }

次を受け入れる。

   null

これは、スキーマが値 "true" の "nullable" メンバーを持つためである。

"nullable" が "false" であることは、本文書で記述される どの形式においても効果を持たないことに注意。たとえば、次の スキーマは

   {
     "definitions": { "a": { "nullable": false, "type": "float32" }},
     "ref": "a",
     "nullable": true
   }

次を受け入れる。

   null

言い換えると、"nullable" に "false" 値を置いても、 "ref" 形式のスキーマにおける "nullable" メンバーを上書きすることはない。 スキーマ内の "nullable" メンバーに "false" の値を持たせることは 正しいが、効果はない。

3.3.3.

"type" 形式は、値がブール値、数値、文字列、または timestamp [RFC3339] であるインスタンスを記述することを意図している。 "type" 形式の構文は Section 2.2.3 で記述される。

スキーマが "type" 形式である場合、次のとおりである。

  • スキーマが "nullable" という名前のメンバーを持ち、 その値がブール値 "true" であり、インスタンスが JSON プリミティブ値 "null" である場合、そのスキーマは インスタンスを受け入れる。

    それ以外の場合、次のとおりである。

    • T を、 "type" という名前を持つメンバーの値とする。次の表は、 T の値の関数として、インスタンスが受け入れられるかどうかを記述する。
    • 表 1: Type に対して受け入れられる値
      "T" が ... に等しい場合 インスタンスは、 ... である場合に受け入れられる
      boolean "true" または "false" に等しい
      float32 JSON 数値
      float64 JSON 数値
      int8 表 2 を参照
      uint8 表 2 を参照
      int16 表 2 を参照
      uint16 表 2 を参照
      int32 表 2 を参照
      uint32 表 2 を参照
      string JSON 文字列
      timestamp [RFC3339] に記述され、 Section 3.3 of [RFC4287] によって精緻化された標準形式に従う JSON 文字列

      "float32" と "float64" は、 それぞれの意図において互いに区別される。 "float32" は IEEE 754 単精度浮動小数点数として処理されることを 意図したデータを示し、"float64" は IEEE 754 倍精度浮動小数点数として処理されることを意図したデータを示す。 JTD スキーマからコードを生成するツールは、"float32" と "float64" に対して異なるコードを生成する可能性が高い。

T が "int" または "uint" で始まる場合、 インスタンスは、ゼロの小数部を持つ値をエンコードする JSON 数値である場合に限り 受け入れられる。T の値に応じて、このエンコードされた数値は さらに特定の範囲内に収まっていなければならない。

表 2: 整数型の範囲
"T" 最小値(含む) 最大値(含む)
int8 -128 127
uint8 0 255
int16 -32,768 32,767
uint16 0 65,535
int32 -2,147,483,648 2,147,483,647
uint32 0 4,294,967,295

次に注意。

   10

および

   10.0

および

   1.0e1

はゼロの小数部を持つ値をエンコードする。一方、

   10.5

はゼロでない小数部を持つ数値をエンコードする。したがって、次の スキーマは

   {"type": "int8"}

次を受け入れ、

   10

および

   10.0

および

   1.0e1

しかし次を拒否し、

   10.5

さらに次も拒否する。

   false

これは、"false" がそもそも数値ではないためである。

インスタンスが受け入れられない場合、この場合のエラー指示子は、 インスタンスを指す "instancePath" と、"type" という名前の スキーマメンバーを指す "schemaPath" を持つものとする。

たとえば、次のスキーマは

   {"type": "boolean"}

次を受け入れるが、

   false

次を拒否する。

   127

次のスキーマは

   {"type": "float32"}

次を受け入れ、

   10.5

および

   127

しかし次を拒否する。

   false

次のスキーマは

   {"type": "string"}

次を受け入れ、

   "1985-04-12T23:20:50.52Z"

および

   "foo"

しかし次を拒否する。

   false

次のスキーマは

   {"type": "timestamp"}

次を受け入れるが、

   "1985-04-12T23:20:50.52Z"

次を拒否し、

   "foo"

および

   false

次のスキーマは

   {"type": "boolean", "nullable": true}

次を受け入れ、

   null

および

   false

しかし次を拒否する。

   127

本節で示した拒否されるインスタンスのすべての例において、 生成すべきエラー指示子は次のとおりである。

   [{ "instancePath": "", "schemaPath": "/type" }]

3.3.4. 列挙

"enum" 形式は、値が所与の文字列値集合の いずれかでなければならないインスタンスを記述することを意図している。 "enum" 形式の構文は Section 2.2.4 で記述される。

スキーマが "enum" 形式である場合、次のとおりである。

  • スキーマが "nullable" という名前のメンバーを持ち、 その値がブール値 "true" であり、インスタンスが JSON プリミティブ値 "null" である場合、そのスキーマは インスタンスを受け入れる。

    それ以外の場合、次のとおりである。

    • E を、 "enum" という名前を持つスキーマメンバーの値とする。 インスタンスは、E の要素の 1 つに等しい場合に限り 受け入れられる。

インスタンスが受け入れられない場合、この場合のエラー指示子は、 インスタンスを指す "instancePath" と、"enum" という名前の スキーマメンバーを指す "schemaPath" を持つものとする。

たとえば、次のスキーマは

   { "enum": ["PENDING", "DONE", "CANCELED"] }

次を受け入れ、

   "PENDING"

および

   "DONE"

および

   "CANCELED"

しかし次のすべてを拒否する。

   0

および

   1

および

   2

および

   "UNKNOWN"

および

   null

エラー指示子は次のとおりである。

   [{ "instancePath": "", "schemaPath": "/enum" }]

次のスキーマは

   { "enum": ["PENDING", "DONE", "CANCELED"], "nullable": true }

次を受け入れ、

   "PENDING"

および

   null

しかし次を拒否し、

   1

および

   "UNKNOWN"

エラー指示子は次のとおりである。

   [{ "instancePath": "", "schemaPath": "/enum" }]

3.3.5. 要素

"elements" 形式は、配列でなければならない インスタンスを記述することを意図している。さらに別のサブスキーマが、 その配列の要素を記述する。"elements" 形式の構文は Section 2.2.5 で記述される。

スキーマが "elements" 形式である場合、次のとおりである。

  • スキーマが "nullable" という名前のメンバーを持ち、 その値がブール値 "true" であり、インスタンスが JSON プリミティブ値 "null" である場合、そのスキーマは インスタンスを受け入れる。

    それ以外の場合、次のとおりである。

    • S を、"elements" という名前を持つ スキーマメンバーの値とする。インスタンスは、次のすべてが真である場合に限り 受け入れられる。

      • インスタンスは 配列である。そうでない場合、この場合のエラー指示子は、 インスタンスを指す "instancePath" と、 "elements" という名前のスキーマメンバーを指す "schemaPath" を持つものとする。
      • インスタンスが 配列である場合、インスタンスのすべての要素は S によって受け入れられなければならない。そうでない場合、 この場合のエラー指示子は、インスタンスの要素に対して S を評価して生じるすべてのエラーの和集合である。

たとえば、次のスキーマは

   {
     "elements": {
       "type": "float32"
     }
   }

次を受け入れ、

   []

および

   [1, 2, 3]

しかし次を拒否し、

   null

エラー指示子は次のとおりである。

   [{ "instancePath": "", "schemaPath": "/elements" }]

さらに次を拒否し、

   [1, 2, "foo", 3, "bar"]

エラー指示子は次のとおりである。

   [
     { "instancePath": "/2", "schemaPath": "/elements/type" },
     { "instancePath": "/4", "schemaPath": "/elements/type" }
   ]

次のスキーマは

   {
     "elements": {
       "type": "float32"
     },
     "nullable": true
   }

次を受け入れ、

   null

および

   []

および

   [1, 2, 3]

しかし次を拒否し、

   [1, 2, "foo", 3, "bar"]

エラー指示子は次のとおりである。

   [
     { "instancePath": "/2", "schemaPath": "/elements/type" },
     { "instancePath": "/4", "schemaPath": "/elements/type" }
   ]

3.3.6. プロパティ

"properties" 形式は、"struct" として使用される JSON オブジェクトを記述することを意図している。"properties" 形式の構文は Section 2.2.6 で記述される。

スキーマが "properties" 形式である場合、次のとおりである。

  • スキーマが "nullable" という名前のメンバーを持ち、 その値がブール値 "true" であり、インスタンスが JSON プリミティブ値 "null" である場合、そのスキーマは インスタンスを受け入れる。

    それ以外の場合、次のとおりである。

    • インスタンスはオブジェクトでなければならない。

      そうでない場合、そのスキーマはインスタンスを拒否する。 この場合のエラー指示子は、インスタンスを指す "instancePath" と、そのようなスキーマメンバーが存在する場合は "properties" という名前のスキーマメンバーを指す "schemaPath" を持つものとする。そのようなメンバーが存在しない場合、 "schemaPath" は "optionalProperties" という名前の スキーマメンバーを指すものとする。

    • インスタンスがオブジェクトであり、スキーマが "properties" という名前のメンバーを持つ場合、P を "properties" という名前のスキーマメンバーの値とする。 Section 2.2.6 により、 P がオブジェクトであることは分かっている。 P 内のすべてのメンバー名について、インスタンス内に 同じ名前のメンバーが存在しなければならない。

      そうでない場合、そのスキーマはインスタンスを拒否する。 この場合のエラー指示子は、インスタンスを指す "instancePath" と、 直前に記述された要件を満たしていない P のメンバーを指す "schemaPath" を持つものとする。

    • インスタンスがオブジェクトである場合、 P を "properties" という名前のスキーマメンバーの値 (存在する場合)とし、O を "optionalProperties" という名前の スキーマメンバーの値(存在する場合)とする。

      インスタンスの各メンバー I について、 P または O 内に I と同じ名前を持つ メンバーを探す。Section 2.2.6 により、PO の両方がそのような メンバーを持つことは不可能であることが分かっている。 "discriminator tag exemption" が I に対して有効である場合 (Section 3.3.8 を参照)、I を無視する。

      それ以外の場合、次のとおりである。

      • P または O にそのようなメンバーが存在せず、 検証が "allow additional properties" モード (Section 3.1 を参照)ではない場合、そのスキーマはインスタンスを拒否する。

        この場合のエラー指示子は、 I を指す "instancePath" と、スキーマを指す "schemaPath" を持つ。

      • P または O にそのようなメンバーが存在する場合、そのメンバーを S と呼ぶ。SI の値を拒否する場合、 そのスキーマはインスタンスを拒否する。

        この場合のエラー指示子は、 SI の値に対して評価して得られる エラー指示子の和集合である。

  • インスタンスがオブジェクトである場合、 上記のリストの第 2 および第 3 の箇条項目から生じる複数の エラーを持つ可能性がある。この場合、エラー指示子はそれらのエラーの 和集合である。

    たとえば、次のスキーマは

       {
         "properties": {
           "a": { "type": "string" },
           "b": { "type": "string" }
         },
         "optionalProperties": {
           "c": { "type": "string" },
           "d": { "type": "string" }
         }
       }
    

    次を受け入れ、

       { "a": "foo", "b": "bar" }
    

    および

       { "a": "foo", "b": "bar", "c": "baz" }
    

    および

       { "a": "foo", "b": "bar", "c": "baz", "d": "quux" }
    

    および

       { "a": "foo", "b": "bar", "d": "quux" }
    

    しかし次を拒否し、

       null
    

    エラー指示子は次のとおりである。

       [{ "instancePath": "", "schemaPath": "/properties" }]
    

    さらに次を拒否し、

       { "b": 3, "c": 3, "e": 3 }
    

    エラー指示子は次のとおりである。

       [
         { "instancePath": "",
           "schemaPath": "/properties/a" },
         { "instancePath": "/b",
           "schemaPath": "/properties/b/type" },
         { "instancePath": "/c",
           "schemaPath": "/optionalProperties/c/type" },
         { "instancePath": "/e",
           "schemaPath": "" }
       ]
    

    代わりに、スキーマが "additionalProperties: true" を 持ち、それ以外は同じである場合、

       {
         "properties": {
           "a": { "type": "string" },
           "b": { "type": "string" }
         },
         "optionalProperties": {
           "c": { "type": "string" },
           "d": { "type": "string" }
         },
         "additionalProperties": true
       }
    

    インスタンスが同じままであれば、

       { "b": 3, "c": 3, "e": 3 }
    

    そのインスタンスをスキーマに対して評価して得られる エラー指示子は次のようになる。

       [
         { "instancePath": "",
           "schemaPath": "/properties/a" },
         { "instancePath": "/b",
           "schemaPath": "/properties/b/type" },
         { "instancePath": "/c",
           "schemaPath": "/optionalProperties/c/type" },
       ]
    

    これらは以前と同じエラーであるが、最後のエラー (インスタンス内の "e" という名前の追加メンバーに関連するもの)は もはや存在しない。これは、"additionalProperties: true" がそのスキーマ上で "allow additional properties" モードを有効にするためである。

    最後に、次のスキーマは

       {
         "nullable": true,
         "properties": {
           "a": { "type": "string" },
           "b": { "type": "string" }
         },
         "optionalProperties": {
           "c": { "type": "string" },
           "d": { "type": "string" }
         },
         "additionalProperties": true
       }
    

    次を受け入れるが、

       null
    

    次を拒否し、

       { "b": 3, "c": 3, "e": 3 }
    

    エラー指示子は次のとおりである。

       [
         { "instancePath": "",
           "schemaPath": "/properties/a" },
         { "instancePath": "/b",
           "schemaPath": "/properties/b/type" },
         { "instancePath": "/c",
           "schemaPath": "/optionalProperties/c/type" },
       ]
    

3.3.7.

"values" 形式は、連想配列として使用される JSON オブジェクトであるインスタンスを記述することを意図している。 "values" 形式の構文は Section 2.2.7 で記述される。

スキーマが "values" 形式である場合、次のとおりである。

  • スキーマが "nullable" という名前のメンバーを持ち、 その値がブール値 "true" であり、インスタンスが JSON プリミティブ値 "null" である場合、そのスキーマは インスタンスを受け入れる。

    それ以外の場合、次のとおりである。

    • S を、"values" という名前を持つ スキーマメンバーの値とする。インスタンスは、次のすべてが真である場合に限り 受け入れられる。

      • インスタンスは オブジェクトである。そうでない場合、この場合のエラー指示子は、 インスタンスを指す "instancePath" と、 "values" という名前のスキーマメンバーを指す "schemaPath" を持つものとする。
      • インスタンスが オブジェクトである場合、インスタンスのすべてのメンバー値は S によって受け入れられなければならない。そうでない場合、 この場合のエラー指示子は、インスタンスのメンバー値に対して S を評価して生じるすべてのエラー指示子の和集合である。

たとえば、次のスキーマは

   {
     "values": {
       "type": "float32"
     }
   }

次を受け入れ、

   {}

および

   {"a": 1, "b": 2}

しかし次を拒否し、

   null

エラー指示子は次のとおりである。

   [{ "instancePath": "", "schemaPath": "/values" }]

さらに次を拒否し、

   { "a": 1, "b": 2, "c": "foo", "d": 3, "e": "bar" }

エラー指示子は次のとおりである。

   [
     { "instancePath": "/c", "schemaPath": "/values/type" },
     { "instancePath": "/e", "schemaPath": "/values/type" }
   ]

次のスキーマは

   {
     "nullable": true,
     "values": {
       "type": "float32"
     }
   }

次を受け入れるが、

   null

次を拒否し、

   { "a": 1, "b": 2, "c": "foo", "d": 3, "e": "bar" }

エラー指示子は次のとおりである。

   [
     { "instancePath": "/c", "schemaPath": "/values/type" },
     { "instancePath": "/e", "schemaPath": "/values/type" }
   ]

3.3.8. 判別子

"discriminator" 形式は、C 系言語における discriminated union 構成に似た方法で使用される JSON オブジェクトを 記述することを意図している。"discriminator" 形式の構文は Section 2.2.8 で記述される。

スキーマが "discriminator" 形式である場合、それは次を検証する。

  • インスタンスがオブジェクトであること、
  • インスタンスが特定の "tag" プロパティを持つこと、
  • この "tag" プロパティの値が、 有効な値の集合内の文字列であること、および
  • インスタンスが別のスキーマを満たすこと。 ここで、この別のスキーマは "tag" プロパティの値に基づいて選択される。

"discriminator" 形式の動作は他のキーワードよりも複雑である。 CDDL に詳しい読者にとって、Appendix B の 最後の例は、その動作を理解する助けになるかもしれない。本節の以下では、 "discriminator" 形式の動作と、いくつかの例を記述する。

スキーマが "discriminator" 形式である場合、次のとおりである。

  • D を、 "discriminator" という名前を持つスキーマメンバーとする。
  • M を、 "mapping" という名前を持つスキーマメンバーとする。
  • I を、 名前が D の値に等しいインスタンスメンバーとする。 I は、一部の拒否されるインスタンスでは存在しない場合がある。
  • S を、 名前が I の値に等しい M のメンバーとする。 S は、一部の拒否されるインスタンスでは存在しない場合がある。

スキーマが "nullable" という名前のメンバーを持ち、その値が ブール値 "true" であり、インスタンスが JSON プリミティブ値 "null" である場合、そのスキーマはインスタンスを受け入れる。 それ以外の場合、インスタンスは次のすべてが真である場合に限り 受け入れられる。

  • インスタンスはオブジェクトである。

    そうでない場合、この場合のエラー指示子は、 インスタンスを指す "instancePath" と、D を指す "schemaPath" を持つものとする。

  • インスタンスが JSON オブジェクトである場合、 I が存在しなければならない。

    そうでない場合、この場合のエラー指示子は、 インスタンスを指す "instancePath" と、D を指す "schemaPath" を持つものとする。

  • インスタンスが JSON オブジェクトであり、 I が存在する場合、I の値は文字列でなければならない。

    そうでない場合、この場合のエラー指示子は、 I を指す "instancePath" と、D を指す "schemaPath" を持つものとする。

  • インスタンスが JSON オブジェクトであり、 I が存在し、文字列値を持つ場合、S が存在しなければならない。

    そうでない場合、この場合のエラー指示子は、 I を指す "instancePath" と、M を指す "schemaPath" を持つものとする。

  • インスタンスが JSON オブジェクトであり、 I が存在し、S が存在する場合、そのインスタンスは S の値を満たさなければならない。Section 2 により、 S の値が "properties" 形式のスキーマであることは分かっている。 インスタンスが S の値を満たすかどうかを評価する際には、 Section 3.3.6 で与えられる "discriminator tag exemption" を I に適用する。

    そうでない場合、この場合のエラー指示子は、 I に "discriminator tag exemption" を適用したうえで、 S の値をインスタンスに対して評価して得られる エラー指示子とする。

上記のリスト項目は相互に排他的な方法で定義されている。 任意のインスタンスとスキーマについて、上記のリスト項目のうち ちょうど 1 つが適用される。

たとえば、次のスキーマは

   {
     "discriminator": "version",
     "mapping": {
       "v1": {
         "properties": {
           "a": { "type": "float32" }
         }
       },
       "v2": {
         "properties": {
           "a": { "type": "string" }
         }
       }
     }
   }

次を拒否し、

   null

エラー指示子は次のとおりである。

   [{ "instancePath": "", "schemaPath": "/discriminator" }]

(これは、インスタンスがオブジェクトではない場合である。)

また、次も拒否される。

   {}

エラー指示子は次のとおりである。

   [{ "instancePath": "", "schemaPath": "/discriminator" }]

(これは、I が存在しない場合である。)

また、次も拒否される。

   { "version": 1 }

エラー指示子は次のとおりである。

   [
     {
       "instancePath": "/version",
       "schemaPath": "/discriminator"
     }
   ]

(これは、I は存在するが文字列値を持たない場合である。)

また、次も拒否される。

   { "version": "v3" }

エラー指示子は次のとおりである。

   [
     {
       "instancePath": "/version",
       "schemaPath": "/mapping"
     }
   ]

(これは、I が存在し文字列値を持つが、 S が存在しない場合である。)

また、次も拒否される。

   { "version": "v2", "a": 3 }

エラー指示子は次のとおりである。

   [
     {
       "instancePath": "/a",
       "schemaPath": "/mapping/v2/properties/a/type"
     }
   ]

(これは、IS は存在するが、 インスタンスが S の値を満たさない場合である。)

最後に、そのスキーマは次を受け入れる。

   { "version": "v2", "a": "foo" }

このインスタンスは、"/mapping/v2/properties" に "version" が記載されていなくても受け入れられる。 "discriminator tag exemption" により、S の値に対して インスタンスを評価する際に、"version" が追加プロパティとして 扱われないことが保証される。

対照的に、同じスキーマで "nullable" が "true" である場合を考える。次のスキーマは

   {
     "nullable": true,
      "discriminator": "version",
      "mapping": {
        "v1": {
          "properties": {
            "a": { "type": "float32" }
          }
        },
        "v2": {
          "properties": {
            "a": { "type": "string" }
          }
        }
      }
   }

次を受け入れる。

   null

"discriminator" 形式をさらに例で示すため、 Section 2.2.8 の JTD スキーマを思い出す。ここに再掲する。

   {
     "discriminator": "event_type",
     "mapping": {
       "account_deleted": {
         "properties": {
           "account_id": { "type": "string" }
         }
       },
       "account_payment_plan_changed": {
         "properties": {
           "account_id": { "type": "string" },
           "payment_plan": { "enum": ["FREE", "PAID"] }
         },
         "optionalProperties": {
           "upgraded_by": { "type": "string" }
         }
       }
     }
   }

このスキーマは次を受け入れ、

   { "event_type": "account_deleted", "account_id": "abc-123" }

および

   {
     "event_type": "account_payment_plan_changed",
     "account_id": "abc-123",
     "payment_plan": "PAID"
   }

および

   {
     "event_type": "account_payment_plan_changed",
     "account_id": "abc-123",
     "payment_plan": "PAID",
     "upgraded_by": "users/mkhwarizmi"
   }

しかし次を拒否し、

   {}

エラー指示子は次のとおりである。

   [{ "instancePath": "", "schemaPath": "/discriminator" }]

さらに次を拒否し、

   { "event_type": "some_other_event_type" }

エラー指示子は次のとおりである。

   [
     {
       "instancePath": "/event_type",
       "schemaPath": "/mapping"
     }
   ]

さらに次を拒否し、

   { "event_type": "account_deleted" }

エラー指示子は次のとおりである。

   [{
     "instancePath": "",
     "schemaPath": "/mapping/account_deleted/properties/account_id"
   }]

さらに次を拒否し、

   {
     "event_type": "account_payment_plan_changed",
     "account_id": "abc-123",
     "payment_plan": "PAID",
     "xxx": "asdf"
   }

エラー指示子は次のとおりである。

   [{
     "instancePath": "/xxx",
     "schemaPath": "/mapping/account_payment_plan_changed"
   }]

4. IANA に関する考慮事項

本文書に IANA の処理はない。

5. セキュリティに関する考慮事項

JTD の実装は、必然的に JSON データを操作することになる。したがって、[RFC8259] のセキュリティに関する考慮事項はすべてここにも関連する。

ユーザーが入力したスキーマを評価する実装は、 素朴な実装を無限ループに陥らせる可能性のある循環参照を検出して中止する メカニズムを実装すべきである(SHOULD)。 そのようなメカニズムがなければ、実装はサービス拒否攻撃に対して 脆弱になる可能性がある。

6. 参考文献

6.1. 規範的参考文献

[RFC2119]
Bradner, S., "RFC において 要求レベルを示すために使用されるキーワード", BCP 14, RFC 2119, DOI 10.17487/RFC2119, , <https://www.rfc-editor.org/info/rfc2119>.
[RFC3339]
Klyne, G. and C. Newman, "インターネット上の日付と時刻: タイムスタンプ", RFC 3339, DOI 10.17487/RFC3339, , <https://www.rfc-editor.org/info/rfc3339>.
[RFC4287]
Nottingham, M., Ed. and R. Sayre, Ed., "Atom 配信形式", RFC 4287, DOI 10.17487/RFC4287, , <https://www.rfc-editor.org/info/rfc4287>.
[RFC6901]
Bryan, P., Ed., Zyp, K., and M. Nottingham, Ed., "JavaScript Object Notation (JSON) Pointer", RFC 6901, DOI 10.17487/RFC6901, , <https://www.rfc-editor.org/info/rfc6901>.
[RFC8174]
Leiba, B., "RFC 2119 キーワードにおける 大文字と小文字の曖昧性", BCP 14, RFC 8174, DOI 10.17487/RFC8174, , <https://www.rfc-editor.org/info/rfc8174>.
[RFC8259]
Bray, T., Ed., "JavaScript Object Notation (JSON) データ交換形式", STD 90, RFC 8259, DOI 10.17487/RFC8259, , <https://www.rfc-editor.org/info/rfc8259>.
[RFC8610]
Birkholz, H., Vigano, C., and C. Bormann, "Concise Data Definition Language (CDDL): Concise Binary Object Representation (CBOR) および JSON データ構造を表現するための記法上の規約", RFC 8610, DOI 10.17487/RFC8610, , <https://www.rfc-editor.org/info/rfc8610>.

6.2. 参考情報としての参考文献

[JSON-SCHEMA]
Wright, A., Andrews, H., Hutton, B., and G. Dennis, "JSON Schema: JSON 文書を記述するためのメディア型", 作業中, Internet-Draft, draft-handrews-json-schema-02, , <https://tools.ietf.org/html/draft-handrews-json-schema-02>.
[OPENAPI]
OpenAPI Initiative, "OpenAPI 仕様", , <https://spec.openapis.org/oas/v3.0.3>.
[RFC7071]
Borenstein, N. and M. Kucherawy, "評判情報交換のためのメディア型", RFC 7071, DOI 10.17487/RFC7071, , <https://www.rfc-editor.org/info/rfc7071>.
[RFC7493]
Bray, T., Ed., "I-JSON メッセージ 形式", RFC 7493, DOI 10.17487/RFC7493, , <https://www.rfc-editor.org/info/rfc7493>.

Appendix A. 省略された 機能の根拠

本付録は規範的ではない。

本節は、JSON Type Definition から意図的に除外された 可能な機能について記述し、なぜこれらの機能が省略されているのかを 正当化する。

A.1. 64 ビット 数値のサポート

本文書は、JTD の "type" キーワードの値として "int64" または "uint64" を許可しない(Sections 2.2.3 および 3.3.3 を参照)。 そのような仮想的な "int64" または "uint64" 型は、 "int32" または "uint32"(それぞれ)と同様に動作するが、 32 ビット整数ではなく 64 ビットに関連する値の範囲を持つ。すなわち、

  • "int64" は -(2**63) から (2**63)-1 までの数値を受け入れる
  • "uint64" は 0 から (2**64)-1 までの数値を受け入れる

"int64" および "uint64" のユーザーは、 符号付きまたは符号なし 64 ビット整数の全範囲を、精度を失うことなく JSON として相互運用可能に送信できると期待する可能性が高い。しかし、 この仮定は、Section 2.2 of [RFC7493] で示される理由により、 誤っている可能性が高い。

"int64" および "uint64" は、64 ビット整数の全範囲を 精度を失うことなく JSON として相互運用可能に処理できる、という誤った仮定を ユーザーに抱かせる可能性が高かった。ユーザーを誤った方向へ導くことを避けるため、 JTD は "int64" および "uint64" を省略している。

A.2. 非ルート 定義のサポート

本文書は、"definitions" キーワードが ルートスキーマの外に現れることを許可しない(Figure 1 を参照)。 考えようによっては、本文書は代わりに、 "definitions" が任意のスキーマ上に、非ルートのものも含めて現れることを 許可できたかもしれない。この代替設計では、"ref" は、 その "ref" を含み、かつ適切な名前の "definitions" メンバーを 持つ "nearest"(すなわち最も入れ子の深い)スキーマ内の 定義へ解決されることになる。

たとえば、この代替手法の下では、 図 3 のようなスキーマを定義できる。

{
  "properties": {
    "foo": {
      "definitions": {
        "user": { "properties": { "user_id": {"type": "string" }}}
      },
      "ref": "user"
    },
    "bar": {
      "definitions": {
        "user": { "properties": { "user_id": {"type": "string" }}}
      },
      "ref": "user"
    },
    "baz": {
      "definitions": {
        "user": { "properties": { "userId": {"type": "string" }}}
      },
      "ref": "user"
    }
  }
}
図 3: 本文書が非ルート 定義を許可していた場合の仮想的なスキーマ。 これは正しい JTD スキーマではない。

図 3 のようなスキーマが 許可されていた場合、JTD スキーマからのコード生成はより困難になり、 生成されたコードはあまり有用でなくなる。

コード生成がより困難になるのは、定義から生成される型について、 コード生成器に名前マングリング方式の実装を強いることになるためである。 この追加の困難は大きなものではないが、本来は比較的単純な作業に 複雑さを加える。

生成されたコードの有用性が低くなるのは、生成されたマングル済みの struct 名が、人間が定義した struct 名ほど簡潔ではないためである。たとえば、 図 3 の "user" 定義は、 "PropertiesFooUser"、 "PropertiesBarUser"、および "PropertiesBazUser" という名前の型へ 生成されていたかもしれない。このような分かりにくい名前は、 "User" のような名前よりも、人間が書くコードにとって有用性が低い。

さらに、"PropertiesFooUser" と "PropertiesBarUser" が本質的に同一であっても、多くの静的型付け プログラミング言語では相互交換可能ではない。コード生成器は、 同一の定義を重複排除することでこれを回避しようとすることもできるが、 その場合、"user_id" ではなく "userId" という名前のプロパティを 許可するスキーマから定義された、微妙に異なる "PropertiesBazUser" が なぜ重複排除されなかったのかについて、ユーザーが混乱する可能性がある。

非ルート定義には実装上および利用上の課題があるように見えること、 そして後から JTD を修正して非ルート定義を許可する方が、 後から JTD を修正してそれらを禁止するより容易であることから、 本文書は JTD スキーマにおける非ルート定義を許可しない。

Appendix B. CDDL との比較

本付録は規範的ではない。

CDDL に詳しい読者を助けるため、本節は、 同じインスタンスを受け入れ、拒否する JTD スキーマと CDDL スキーマを提示することで、 JTD がどのように機能するかを示す。

次の JTD スキーマは

   {}

次の CDDL 規則と同じインスタンスを受け入れる。

   root = any

次の JTD スキーマは

   {
     "definitions": {
       "a": { "elements": { "ref": "b" }},
       "b": { "type": "float32" }
     },
     "elements": {
       "ref": "a"
     }
   }

次の CDDL 規則と同じインスタンスを受け入れる。

   root = [* a]
   a = [* b]
   b = number

次の JTD スキーマは

   { "enum": ["PENDING", "DONE", "CANCELED"]}

次の CDDL 規則と同じインスタンスを受け入れる。

   root = "PENDING" / "DONE" / "CANCELED"

次の JTD スキーマは

   {"type": "boolean"}

次の CDDL 規則と同じインスタンスを受け入れる。

   root = bool

次の JTD スキーマは、

   {"type": "float32"}

および

   {"type": "float64"}

いずれも次の CDDL 規則と同じインスタンスを受け入れる。

   root = number

次の JTD スキーマは

   {"type": "string"}

次の CDDL 規則と同じインスタンスを受け入れる。

   root = tstr

次の JTD スキーマは

   {"type": "timestamp"}

次の CDDL 規則と同じインスタンスを受け入れる。

   root = tdate

次の JTD スキーマは

   { "elements": { "type": "float32" }}

次の CDDL 規則と同じインスタンスを受け入れる。

   root = [* number]

次の JTD スキーマは

   {
     "properties": {
       "a": { "type": "boolean" },
       "b": { "type": "float32" }
     },
     "optionalProperties": {
       "c": { "type": "string" },
       "d": { "type": "timestamp" }
     }
   }

次の CDDL 規則と同じインスタンスを受け入れる。

   root = { a: bool, b: number, ? c: tstr, ? d: tdate }

次の JTD スキーマは

   { "values": { "type": "float32" }}

次の CDDL 規則と同じインスタンスを受け入れる。

   root = { * tstr => number }

最後に、次の JTD スキーマは

   {
     "discriminator": "a",
     "mapping": {
       "foo": {
         "properties": {
           "b": { "type": "float32" }
         }
       },
       "bar": {
         "properties": {
           "b": { "type": "string" }
         }
       }
     }
   }

次の CDDL 規則と同じインスタンスを受け入れる。

   root = { a: "foo", b: number } / { a: "bar", b: tstr }

Appendix C.

本付録は規範的ではない。

JTD の実例として、図 4 に、 Section 6.2.2 of [RFC7071] で記述される 平易な英語の定義 "reputation-object" とほぼ同等の JTD スキーマを示す。

{
  "properties": {
    "application": { "type": "string" },
    "reputons": {
      "elements": {
        "additionalProperties": true,
        "properties": {
          "rater": { "type": "string" },
          "assertion": { "type": "string" },
          "rated": { "type": "string" },
          "rating": { "type": "float32" },
        },
        "optionalProperties": {
          "confidence": { "type": "float32" },
          "normal-rating": { "type": "float32" },
          "sample-size": { "type": "float64" },
          "generated": { "type": "float64" },
          "expires": { "type": "float64" }
        }
      }
    }
  }
}
図 4: Section 6.2.2 of [RFC7071] の "reputation-object" を記述する JTD スキーマ

このスキーマは、 "sample-size"、"generated"、および "expires" が 非有界の正の整数であるという要件を強制しない。また、 "rating"、"confidence"、および "normal-rating" が 小数点以下 3 桁を超える精度を持つべきではないという制限を表現しない。

図 4 の例は、 Appendix H of [RFC8610] における同等の例と比較できる。

謝辞

Carsten Bormann は、 JTD の設計および本文書の構成について、多くの有用な 指導とフィードバックを提供した。

Evgeny Poberezkin は、 "nullable" の追加を提案し、誤りや簡素化の機会について 本文書を徹底的に精査した。

Tim Bray は、現在の "ref" モデルと "enum" の追加を提案した。Anders Rundgren は、 数値型に対するより多くのサポートを持つよう "type" を 拡張することを提案した。James Manger は、 整数型の動作方法に関する追加の明確化例を提案した。Adrian Farrel は、 本文書をより明確にするための多くの改善を提案した。

IETF JSON メーリングリストのメンバー、特に Pete CordellPhillip Hallam-BakerNico WilliamsJohn CowanRob Sayre、および Erik Wilde は、多くの有用な フィードバックを提供した。

OpenAPI の "discriminator" オブジェクト [OPENAPI] は、"discriminator" 形式の着想源となった。[JSON-SCHEMA] は、JTD の初期設計の さまざまな部分に影響を与えた。

著者の連絡先

Ulysse Carion
Segment.io, Inc
100 California Street
San Francisco, CA 94111
アメリカ合衆国