summaryrefslogtreecommitdiff
path: root/doc/rfc/rfc8927.txt
diff options
context:
space:
mode:
Diffstat (limited to 'doc/rfc/rfc8927.txt')
-rw-r--r--doc/rfc/rfc8927.txt2333
1 files changed, 2333 insertions, 0 deletions
diff --git a/doc/rfc/rfc8927.txt b/doc/rfc/rfc8927.txt
new file mode 100644
index 0000000..785f6e8
--- /dev/null
+++ b/doc/rfc/rfc8927.txt
@@ -0,0 +1,2333 @@
+
+
+
+
+Independent Submission U. Carion
+Request for Comments: 8927 Segment
+Category: Experimental November 2020
+ISSN: 2070-1721
+
+
+ JSON Type Definition
+
+Abstract
+
+ This document proposes a format, called JSON Type Definition (JTD),
+ for describing the shape of JavaScript Object Notation (JSON)
+ messages. Its main goals are to enable code generation from schemas
+ as well as portable validation with standardized error indicators.
+ To this end, JTD is intentionally limited to be no more expressive
+ than the type systems of mainstream programming languages. This
+ intentional limitation, as well as the decision to make JTD schemas
+ be JSON documents, makes tooling atop of JTD easier to build.
+
+ This document does not have IETF consensus and is presented here to
+ facilitate experimentation with the concept of JTD.
+
+Status of This Memo
+
+ This document is not an Internet Standards Track specification; it is
+ published for examination, experimental implementation, and
+ evaluation.
+
+ This document defines an Experimental Protocol for the Internet
+ community. This is a contribution to the RFC Series, independently
+ of any other RFC stream. The RFC Editor has chosen to publish this
+ document at its discretion and makes no statement about its value for
+ implementation or deployment. Documents approved for publication by
+ the RFC Editor are not candidates for any level of Internet Standard;
+ see Section 2 of RFC 7841.
+
+ Information about the current status of this document, any errata,
+ and how to provide feedback on it may be obtained at
+ https://www.rfc-editor.org/info/rfc8927.
+
+Copyright Notice
+
+ Copyright (c) 2020 IETF Trust and the persons identified as the
+ document authors. All rights reserved.
+
+ This document is subject to BCP 78 and the IETF Trust's Legal
+ Provisions Relating to IETF Documents
+ (https://trustee.ietf.org/license-info) in effect on the date of
+ publication of this document. Please review these documents
+ carefully, as they describe your rights and restrictions with respect
+ to this document.
+
+Table of Contents
+
+ 1. Introduction
+ 1.1. Terminology
+ 1.2. Scope of Experiment
+ 2. Syntax
+ 2.1. Root vs. Non-root Schemas
+ 2.2. Forms
+ 2.2.1. Empty
+ 2.2.2. Ref
+ 2.2.3. Type
+ 2.2.4. Enum
+ 2.2.5. Elements
+ 2.2.6. Properties
+ 2.2.7. Values
+ 2.2.8. Discriminator
+ 2.3. Extending JTD's Syntax
+ 3. Semantics
+ 3.1. Allowing Additional Properties
+ 3.2. Errors
+ 3.3. Forms
+ 3.3.1. Empty
+ 3.3.2. Ref
+ 3.3.3. Type
+ 3.3.4. Enum
+ 3.3.5. Elements
+ 3.3.6. Properties
+ 3.3.7. Values
+ 3.3.8. Discriminator
+ 4. IANA Considerations
+ 5. Security Considerations
+ 6. References
+ 6.1. Normative References
+ 6.2. Informative References
+ Appendix A. Rationale for Omitted Features
+ A.1. Support for 64-Bit Numbers
+ A.2. Support for Non-root Definitions
+ Appendix B. Comparison with CDDL
+ Appendix C. Example
+ Acknowledgments
+ Author's Address
+
+1. Introduction
+
+ This document describes a schema language for JSON [RFC8259] called
+ JSON Type Definition (JTD).
+
+ There exist many options for describing JSON data. JTD's niche is to
+ focus on enabling code generation from schemas; to this end, JTD's
+ expressiveness is intentionally limited to be no more powerful than
+ what can be expressed in the type systems of mainstream programming
+ languages.
+
+ The goals of JTD are to:
+
+ * Provide an unambiguous description of the overall structure of a
+ JSON document.
+
+ * Be able to describe common JSON data types and structures (that
+ is, the data types and structures necessary to support most JSON
+ documents and that are widely understood in an interoperable way
+ by JSON implementations).
+
+ * Provide a single format that is readable and editable by both
+ humans and machines and that can be embedded within other JSON
+ documents. This makes JTD a convenient format for tooling to
+ accept as input or produce as output.
+
+ * Enable code generation from JTD schemas. JTD schemas are meant to
+ be easy to convert into data structures idiomatic to mainstream
+ programming languages.
+
+ * Provide a standardized format for error indicators when data does
+ not conform with a schema.
+
+ JTD is intentionally designed as a rather minimal schema language.
+ Thus, although JTD can describe some categories of JSON, it is not
+ able to describe its own structure; this document uses Concise Data
+ Definition Language (CDDL) [RFC8610] to describe JTD's syntax. By
+ keeping the expressiveness of the schema language minimal, JTD makes
+ code generation and standardized error indicators easier to
+ implement.
+
+ Examples in this document use constructs from the C++ programming
+ language. These examples are provided to aid the reader in
+ understanding the principles of JTD but are not limiting in any way.
+
+ JTD's feature set is designed to represent common patterns in JSON-
+ using applications, while still having a clear correspondence to
+ programming languages in widespread use. Thus, JTD supports:
+
+ * Signed and unsigned 8-, 16-, and 32-bit integers. A tool that
+ converts JTD schemas into code can use "int8_t", "uint8_t",
+ "int16_t", etc., or their equivalents in the target language, to
+ represent these JTD types.
+
+ * A distinction between "float32" and "float64". Code generators
+ can use "float" and "double", or their equivalents, for these JTD
+ types.
+
+ * A "properties" form of JSON objects, corresponding to some sort of
+ struct or record. The "properties" form of JSON objects is akin
+ to a C++ "struct".
+
+ * A "values" form of JSON objects, corresponding to some sort of
+ dictionary or associative array. The "values" form of JSON
+ objects is akin to a C++ "std::map".
+
+ * A "discriminator" form of JSON objects, corresponding to a
+ discriminated (or "tagged") union. The "discriminator" form of
+ JSON objects is akin to a C++ "std::variant".
+
+ The principle of common patterns in JSON is why JTD does not support
+ 64-bit integers, as these are usually transmitted over JSON in non-
+ interoperable (i.e., ignoring the recommendations in Section 2.2 of
+ [RFC7493]) or mutually inconsistent ways. Appendix A.1 further
+ elaborates on why JTD does not support 64-bit integers.
+
+ The principle of clear correspondence to common programming languages
+ is why JTD does not support, for example, a data type for integers up
+ to 2**53-1.
+
+ It is expected that for many use cases, a schema language of JTD's
+ expressiveness is sufficient. Where a more expressive language is
+ required, alternatives exist in CDDL and others.
+
+ This document does not have IETF consensus and is presented here to
+ facilitate experimentation with the concept of JTD. The purpose of
+ the experiment is to gain experience with JTD and to possibly revise
+ this work accordingly. If JTD is determined to be a valuable and
+ popular approach, it may be taken to the IETF for further discussion
+ and revision.
+
+ This document has the following structure. Section 2 defines the
+ syntax of JTD. Section 3 describes the semantics of JTD; this
+ includes determining whether some data satisfies a schema and what
+ error indicators should be produced when the data is unsatisfactory.
+ Appendix A discusses why certain features are omitted from JTD.
+ Appendix B presents various JTD schemas and their CDDL equivalents.
+
+1.1. Terminology
+
+ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
+ "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and
+ "OPTIONAL" in this document are to be interpreted as described in
+ BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all
+ capitals, as shown here.
+
+ The term "JSON Pointer", when it appears in this document, is to be
+ understood as it is defined in [RFC6901].
+
+ The terms "object", "member", "array", "number", "name", and "string"
+ in this document are to be interpreted as described in [RFC8259].
+
+ The term "instance", when it appears in this document, refers to a
+ JSON value being validated against a JTD schema. This value can be
+ an entire JSON document, or it can be a value embedded within a JSON
+ document.
+
+1.2. Scope of Experiment
+
+ JTD is an experiment. Participation in this experiment consists of
+ using JTD to validate or document interchanged JSON messages or
+ building tooling atop of JTD. Feedback on the results of this
+ experiment may be emailed to the author. Participants in this
+ experiment are anticipated to mostly be nodes that provide or consume
+ JSON-based APIs.
+
+ Nodes know if they are participating in the experiment if they are
+ validating JSON messages against a JTD schema or if they are relying
+ on another node to do so. Nodes are also participating in the
+ experiment if they are running code generated from a JTD schema.
+
+ The risk of this experiment "escaping" takes the form of a JTD-
+ supporting node expecting another node, which lacks such support, to
+ validate messages against some JTD schema. In such a case, the
+ outcome will likely be that the nodes fail to interchange information
+ correctly.
+
+ This experiment will be deemed successful when JTD has been
+ implemented by multiple independent parties and these parties
+ successfully use JTD to facilitate information interchange within
+ their internal systems or between systems operated by independent
+ parties.
+
+ If this experiment is deemed successful, and JTD is determined to be
+ a valuable and popular approach, it may be taken to the IETF for
+ further discussion and revision. One possible outcome of this
+ discussion and revision could be that a working group produces a
+ Standards Track specification of JTD.
+
+ Some implementations of JTD, as well as code generators and other
+ tooling related to JTD, are available at <https://github.com/
+ jsontypedef>.
+
+2. Syntax
+
+ This section describes when a JSON document is a correct JTD schema.
+ Because Concise Data Definition Language (CDDL) is well suited to the
+ task of defining complex JSON formats, such as JTD schemas, this
+ section uses CDDL to describe the format of JTD schemas.
+
+ JTD schemas may recursively contain other schemas. In this document,
+ a "root schema" is one that is not contained within another schema,
+ i.e., it is "top level".
+
+ A JTD schema is a JSON object taking on an appropriate form. JTD
+ schemas may contain "additional data", discussed in Section 2.3.
+ Root JTD schemas may optionally contain definitions (a mapping from
+ names to schemas).
+
+ A correct root JTD schema MUST match the "root-schema" CDDL rule
+ described in this section. A correct non-root JTD schema MUST match
+ the "schema" CDDL rule described in this section.
+
+ ; 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,
+ )
+
+ Figure 1: CDDL Definition of a Schema
+
+ The remainder of this section will describe constraints on JTD
+ schemas that cannot be expressed in CDDL. It will also provide
+ examples of valid and invalid JTD schemas.
+
+2.1. Root vs. Non-root Schemas
+
+ The "root-schema" rule in Figure 1 permits a member named
+ "definitions", but the "schema" rule does not permit for such a
+ member. This means that only root (i.e., "top-level") JTD schemas
+ can have a "definitions" object, and subschemas may not.
+
+ Thus,
+
+ { "definitions": {} }
+
+ is a correct JTD schema, but
+
+ {
+ "definitions": {
+ "foo": {
+ "definitions": {}
+ }
+ }
+ }
+
+ is not, because subschemas (such as the object at "/definitions/foo")
+ must not have a member named "definitions".
+
+2.2. Forms
+
+ JTD schemas (i.e., JSON objects satisfying the "schema" CDDL rule in
+ Figure 1) must take on one of eight forms. These forms are defined
+ so as to be mutually exclusive; a schema cannot satisfy multiple
+ forms at once.
+
+2.2.1. Empty
+
+ The "empty" form is defined by the "empty" CDDL rule in Figure 1.
+ The semantics of the "empty" form are described in Section 3.3.1.
+
+ Despite the name "empty", schemas of the "empty" form are not
+ necessarily empty JSON objects. Like schemas of any of the eight
+ forms, schemas of the "empty" form may contain members named
+ "nullable" (whose value must be "true" or "false") or "metadata"
+ (whose value must be an object) or both.
+
+ Thus,
+
+ {}
+
+ and
+
+ { "nullable": true }
+
+ and
+
+ { "nullable": true, "metadata": { "foo": "bar" }}
+
+ are correct JTD schemas of the "empty" form, but
+
+ { "nullable": "foo" }
+
+ is not, because the value of the member named "nullable" must be
+ "true" or "false".
+
+2.2.2. Ref
+
+ The "ref" form is defined by the "ref" CDDL rule in Figure 1. The
+ semantics of the "ref" form are described in Section 3.3.2.
+
+ For a schema of the "ref" form to be correct, the value of the member
+ named "ref" must refer to one of the definitions found at the root
+ level of the schema it appears in. More formally, for a schema _S_
+ of the "ref" form:
+
+ * Let _B_ be the root schema containing the schema or the schema
+ itself if it is a root schema.
+
+ * Let _R_ be the value of the member of _S_ with the name "ref".
+
+ If the schema is correct, then _B_ MUST have a member _D_ with the
+ name "definitions", and _D_ MUST contain a member whose name equals
+ _R_.
+
+ Thus,
+
+ {
+ "definitions": {
+ "coordinates": {
+ "properties": {
+ "lat": { "type": "float32" },
+ "lng": { "type": "float32" }
+ }
+ }
+ },
+ "properties": {
+ "user_location": { "ref": "coordinates" },
+ "server_location": { "ref": "coordinates" }
+ }
+ }
+
+ is a correct JTD schema and demonstrates the point of the "ref" form:
+ to avoid redefining the same thing twice. However,
+
+ { "ref": "foo" }
+
+ is not a correct JTD schema, as there are no top-level "definitions",
+ and so the "ref" form cannot be correct. Similarly,
+
+ { "definitions": { "foo": {}}, "ref": "bar" }
+
+ is not a correct JTD schema, as there is no member named "bar" in the
+ top-level "definitions".
+
+2.2.3. Type
+
+ The "type" form is defined by the "type" CDDL rule in Figure 1. The
+ semantics of the "type" form are described in Section 3.3.3.
+
+ As an example of a correct JTD schema of the "type" form,
+
+ { "type": "uint8" }
+
+ is a correct JTD schema, whereas
+
+ { "type": true }
+
+ and
+
+ { "type": "foo" }
+
+ are not correct schemas, as neither "true" nor the JSON string "foo"
+ are in the list of permitted values of the "type" member described in
+ the "type" CDDL rule in Figure 1.
+
+2.2.4. Enum
+
+ The "enum" form is defined by the "enum" CDDL rule in Figure 1. The
+ semantics of the "enum" form are described in Section 3.3.4.
+
+ For a schema of the "enum" form to be correct, the value of the
+ member named "enum" must be a nonempty array of strings, and that
+ array must not contain duplicate values. More formally, for a schema
+ _S_ of the "enum" form:
+
+ * Let _E_ be the value of the member of _S_ with name "enum".
+
+ If the schema is correct, then there MUST NOT exist any pair of
+ elements of _E_ that encode equal string values, where string
+ equality is defined as in Section 8.3 of [RFC8259].
+
+ Thus,
+
+ { "enum": [] }
+
+ is not a correct JTD schema, as the value of the member named "enum"
+ must be nonempty, and
+
+ { "enum": ["a\\b", "a\u005Cb"] }
+
+ is not a correct JTD schema, as
+
+ "a\\b"
+
+ and
+
+ "a\u005Cb"
+
+ encode strings that are equal by the definition of string equality
+ given in Section 8.3 of [RFC8259]. By contrast,
+
+ { "enum": ["PENDING", "IN_PROGRESS", "DONE" ]}
+
+ is an example of a correct JTD schema of the "enum" form.
+
+2.2.5. Elements
+
+ The "elements" form is defined by the "elements" CDDL rule in
+ Figure 1. The semantics of the "elements" form are described in
+ Section 3.3.5.
+
+ As an example of a correct JTD schema of the "elements" form,
+
+ { "elements": { "type": "uint8" }}
+
+ is a correct JTD schema, whereas
+
+ { "elements": true }
+
+ and
+
+ { "elements": { "type": "foo" } }
+
+ are not correct schemas, as neither
+
+ true
+
+ nor
+
+ { "type": "foo" }
+
+ are correct JTD schemas, and the value of the member named "elements"
+ must be a correct JTD schema.
+
+2.2.6. Properties
+
+ The "properties" form is defined by the "properties" CDDL rule in
+ Figure 1. The semantics of the "properties" form are described in
+ Section 3.3.6.
+
+ For a schema of the "properties" form to be correct, properties must
+ either be required (i.e., in "properties") or optional (i.e., in
+ "optionalProperties"), but not both.
+
+ More formally, if a schema has both a member named "properties" (with
+ value _P_) and another member named "optionalProperties" (with value
+ _O_), then _O_ and _P_ MUST NOT have any member names in common; that
+ is, no member of _P_ may have a name equal to the name of any member
+ of _O_, under the definition of string equality given in Section 8.3
+ of [RFC8259].
+
+ Thus,
+
+ {
+ "properties": { "confusing": {} },
+ "optionalProperties": { "confusing": {} }
+ }
+
+ is not a correct JTD schema, as "confusing" appears in both
+ "properties" and "optionalProperties". By contrast,
+
+ {
+ "properties": {
+ "users": {
+ "elements": {
+ "properties": {
+ "id": { "type": "string" },
+ "name": { "type": "string" },
+ "create_time": { "type": "timestamp" }
+ },
+ "optionalProperties": {
+ "delete_time": { "type": "timestamp" }
+ }
+ }
+ },
+ "next_page_token": { "type": "string" }
+ }
+ }
+
+ is a correct JTD schema of the "properties" form, describing a
+ paginated list of users and demonstrating the recursive nature of the
+ syntax of JTD schemas.
+
+2.2.7. Values
+
+ The "values" form is defined by the "values" CDDL rule in Figure 1.
+ The semantics of the "values" form are described in Section 3.3.7.
+
+ As an example of a correct JTD schema of the "values" form,
+
+ { "values": { "type": "uint8" }}
+
+ is a correct JTD schema, whereas
+
+ { "values": true }
+
+ and
+
+ { "values": { "type": "foo" } }
+
+ are not correct schemas, as neither
+
+ true
+
+ nor
+
+ { "type": "foo" }
+
+ are correct JTD schemas, and the value of the member named "values"
+ must be a correct JTD schema.
+
+2.2.8. Discriminator
+
+ The "discriminator" form is defined by the "discriminator" CDDL rule
+ in Figure 1. The semantics of the "discriminator" form are described
+ in Section 3.3.8. Understanding the semantics of the "discriminator"
+ form will likely aid the reader in understanding why this section
+ provides constraints on the "discriminator" form beyond those in
+ Figure 1.
+
+ To prevent ambiguous or unsatisfiable constraints on the
+ "discriminator" property of a tagged union, an additional constraint
+ on schemas of the "discriminator" form exists. For schemas of the
+ "discriminator" form:
+
+ * Let _D_ be the member of the schema with the name "discriminator".
+
+ * Let _M_ be the member of the schema with the name "mapping".
+
+ If the schema is correct, then all member values _S_ of _M_ will be
+ schemas of the "properties" form. For each _S_:
+
+ * If _S_ has a member _N_ whose name equals "nullable", _N_'s value
+ MUST NOT be the JSON primitive value "true".
+
+ * For each member _P_ of _S_ whose name equals "properties" or
+ "optionalProperties", _P_'s value, which must be an object, MUST
+ NOT contain any members whose name equals _D_'s value.
+
+ Thus,
+
+ {
+ "discriminator": "event_type",
+ "mapping": {
+ "can_the_object_be_null_or_not?": {
+ "nullable": true,
+ "properties": { "foo": { "type": "string" } }}
+ }
+ }
+ }
+
+ is an incorrect schema, as a member of "mapping" has a member named
+ "nullable" whose value is "true". This would suggest that the
+ instance may be null. Yet, the top-level schema lacks such a
+ "nullable" set to "true", which would suggest that the instance in
+ fact cannot be null. If this were a correct JTD schema, it would be
+ unclear which piece of information takes precedence.
+
+ JTD handles such possible ambiguity by disallowing, at the syntactic
+ level, the possibility of contradictory specifications of whether an
+ instance described by a schema of the "discriminator" form may be
+ null. The schemas in a discriminator "mapping" cannot have
+ "nullable" set to "true"; only the discriminator itself can use
+ "nullable" in this way.
+
+ It also follows that
+
+ {
+ "discriminator": "event_type",
+ "mapping": {
+ "is_event_type_a_string_or_a_float32?": {
+ "properties": { "event_type": { "type": "float32" }}
+ }
+ }
+ }
+
+ and
+
+ {
+ "discriminator": "event_type",
+ "mapping": {
+ "is_event_type_a_string_or_an_optional_float32?": {
+ "optionalProperties": { "event_type": { "type": "float32" }}
+ }
+ }
+ }
+
+ are incorrect schemas, as "event_type" is both the value of
+ "discriminator" and a member name in one of the "mapping" member
+ "properties" or "optionalProperties". This is ambiguous, because
+ ordinarily the "discriminator" keyword would indicate that
+ "event_type" is expected to be a string, but another part of the
+ schema specifies that "event_type" is expected to be a number.
+
+ JTD handles such possible ambiguity by disallowing, at the syntactic
+ level, the possibility of contradictory specifications of
+ discriminator "tags". Discriminator "tags" cannot be redefined in
+ other parts of the schema.
+
+ By contrast,
+
+ {
+ "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" }
+ }
+ }
+ }
+ }
+
+ is a correct schema, describing a pattern of data common in JSON-
+ based messaging systems. Section 3.3.8 provides examples of what
+ this schema accepts and rejects.
+
+2.3. Extending JTD's Syntax
+
+ This document does not describe any extension mechanisms for JTD
+ schema validation, which is described in Section 3. However, schemas
+ are defined to optionally contain a "metadata" keyword, whose value
+ is an arbitrary JSON object. Call the members of this object
+ "metadata members".
+
+ Users MAY add metadata members to JTD schemas to convey information
+ that is not pertinent to validation. For example, such metadata
+ members could provide hints to code generators or trigger some
+ special behavior for a library that generates user interfaces from
+ schemas.
+
+ Users SHOULD NOT expect metadata members to be understood by other
+ parties. As a result, if consistent validation with other parties is
+ a requirement, users MUST NOT use metadata members to affect how
+ schema validation, as described in Section 3, works.
+
+ Users MAY expect metadata members to be understood by other parties
+ and MAY use metadata members to affect how schema validation works,
+ if these other parties are somehow known to support these metadata
+ members. For example, two parties may agree, out of band, that they
+ will support an extended JTD with a custom metadata member that
+ affects validation.
+
+3. Semantics
+
+ This section describes when an instance is valid against a correct
+ JTD schema and the error indicators to produce when an instance is
+ invalid.
+
+3.1. Allowing Additional Properties
+
+ Users will have different desired behavior with respect to
+ "unspecified" members in an instance. For example, consider the JTD
+ schema in Figure 2:
+
+ { "properties": { "a": { "type": "string" }}}
+
+ Figure 2: An Illustrative JTD Schema
+
+ Some users may expect that
+
+ {"a": "foo", "b": "bar"}
+
+ satisfies the schema in Figure 2. Others may disagree, as "b" is not
+ one of the properties described in the schema. In this document,
+ allowing such "unspecified" members, like "b" in this example,
+ happens when evaluation is in "allow additional properties" mode.
+
+ Evaluation of a schema does not allow additional properties by
+ default, but this can be overridden by having the schema include a
+ member named "additionalProperties", where that member has a value of
+ "true".
+
+ More formally, evaluation of a schema _S_ is in "allow additional
+ properties" mode if there exists a member of _S_ whose name equals
+ "additionalProperties" and whose value is a boolean "true".
+ Otherwise, evaluation of _S_ is not in "allow additional properties"
+ mode.
+
+ See Section 3.3.6 for how allowing unknown properties affects schema
+ evaluation, but briefly, the schema
+
+ { "properties": { "a": { "type": "string" }}}
+
+ rejects
+
+ { "a": "foo", "b": "bar" }
+
+ However, the schema
+
+ {
+ "additionalProperties": true,
+ "properties": { "a": { "type": "string" }}
+ }
+
+ accepts
+
+ { "a": "foo", "b": "bar" }
+
+ Note that "additionalProperties" does not get "inherited" by
+ subschemas. For example, the JTD schema
+
+ {
+ "additionalProperties": true,
+ "properties": {
+ "a": {
+ "properties": {
+ "b": { "type": "string" }
+ }
+ }
+ }
+ }
+
+ accepts
+
+ { "a": { "b": "c" }, "foo": "bar" }
+
+ but rejects
+
+ { "a": { "b": "c", "foo": "bar" }}
+
+ because the "additionalProperties" at the root level does not affect
+ the behavior of subschemas.
+
+ Note from Figure 1 that only schemas of the "properties" form may
+ have a member named "additionalProperties".
+
+3.2. Errors
+
+ To facilitate consistent validation error handling, this document
+ specifies a standard error indicator format. Implementations SHOULD
+ support producing error indicators in this standard form.
+
+ The standard error indicator format is a JSON array. The order of
+ the elements of this array is not specified. The elements of this
+ array are JSON objects with:
+
+ * A member with the name "instancePath", whose value is a JSON
+ string encoding a JSON Pointer. This JSON Pointer will point to
+ the part of the instance that was rejected.
+
+ * A member with the name "schemaPath", whose value is a JSON string
+ encoding a JSON Pointer. This JSON Pointer will point to the part
+ of the schema that rejected the instance.
+
+ The values for "instancePath" and "schemaPath" depend on the form of
+ the schema and are described in detail in Section 3.3.
+
+3.3. Forms
+
+ This section describes, for each of the eight JTD schema forms, the
+ rules dictating whether an instance is accepted, as well as the error
+ indicators to produce when an instance is invalid.
+
+ The forms a correct schema may take on are formally described in
+ Section 2.
+
+3.3.1. Empty
+
+ The "empty" form is meant to describe instances whose values are
+ unknown, unpredictable, or otherwise unconstrained by the schema.
+ The syntax of the "empty" form is described in Section 2.2.1.
+
+ If a schema is of the "empty" form, then it accepts all instances. A
+ schema of the "empty" form will never produce any error indicators.
+
+3.3.2. Ref
+
+ The "ref" form is for when a schema is defined in terms of something
+ in the "definitions" of the root schema. The "ref" form enables
+ schemas to be less repetitive and also enables describing recursive
+ structures. The syntax of the "ref" form is described in
+ Section 2.2.2.
+
+ If a schema is of the "ref" form, then:
+
+ * If the schema has a member named "nullable" whose value is the
+ boolean "true", and the instance is the JSON primitive value
+ "null", then the schema accepts the instance.
+
+ Otherwise:
+
+ - Let _R_ be the value of the schema member with the name "ref".
+
+ - Let _B_ be the root schema containing the schema or the schema
+ itself if it is a root schema.
+
+ - Let _D_ be the member of _B_ with the name "definitions". Per
+ Section 2, we know _D_ exists.
+
+ - Let _S_ be the value of the member of _D_ whose name equals
+ _R_. Per Section 2.2.2, we know _S_ exists and is a schema.
+
+ The schema accepts the instance if and only if _S_ accepts the
+ instance. Otherwise, the error indicators to return in this case are
+ the union of the error indicators from evaluating _S_ against the
+ instance.
+
+ For example, the schema
+
+ {
+ "definitions": { "a": { "type": "float32" }},
+ "ref": "a"
+ }
+
+ accepts
+
+ 123
+
+ but rejects
+
+ null
+
+ with the error indicator
+
+ [{ "instancePath": "", "schemaPath": "/definitions/a/type" }]
+
+ The schema
+
+ {
+ "definitions": { "a": { "type": "float32" }},
+ "ref": "a",
+ "nullable": true
+ }
+
+ accepts
+
+ null
+
+ because the schema has a "nullable" member whose value is "true".
+
+ Note that "nullable" being "false" has no effect in any of the forms
+ described in this document. For example, the schema
+
+ {
+ "definitions": { "a": { "nullable": false, "type": "float32" }},
+ "ref": "a",
+ "nullable": true
+ }
+
+ accepts
+
+ null
+
+ In other words, it is not the case that putting a "false" value for
+ "nullable" will ever override a "nullable" member in schemas of the
+ "ref" form; it is correct, though ineffectual, to have a value of
+ "false" for the "nullable" member in a schema.
+
+3.3.3. Type
+
+ The "type" form is meant to describe instances whose value is a
+ boolean, number, string, or timestamp [RFC3339]. The syntax of the
+ "type" form is described in Section 2.2.3.
+
+ If a schema is of the "type" form, then:
+
+ * If the schema has a member named "nullable" whose value is the
+ boolean "true", and the instance is the JSON primitive value
+ "null", then the schema accepts the instance.
+
+ Otherwise:
+
+ Let _T_ be the value of the member with the name "type". The
+ following table describes whether the instance is accepted, as
+ a function of _T_'s value:
+
+ +============+=========================================+
+ | If _"T"_ | then the instance is accepted if it is |
+ | equals ... | ... |
+ +============+=========================================+
+ | boolean | equal to "true" or "false" |
+ +------------+-----------------------------------------+
+ | float32 | a JSON number |
+ +------------+-----------------------------------------+
+ | float64 | a JSON number |
+ +------------+-----------------------------------------+
+ | int8 | See Table 2 |
+ +------------+-----------------------------------------+
+ | uint8 | See Table 2 |
+ +------------+-----------------------------------------+
+ | int16 | See Table 2 |
+ +------------+-----------------------------------------+
+ | uint16 | See Table 2 |
+ +------------+-----------------------------------------+
+ | int32 | See Table 2 |
+ +------------+-----------------------------------------+
+ | uint32 | See Table 2 |
+ +------------+-----------------------------------------+
+ | string | a JSON string |
+ +------------+-----------------------------------------+
+ | timestamp | a JSON string that follows the standard |
+ | | format described in [RFC3339], as |
+ | | refined by Section 3.3 of [RFC4287] |
+ +------------+-----------------------------------------+
+
+ Table 1: Accepted Values for Type
+
+ "float32" and "float64" are distinguished from each other in
+ their intent. "float32" indicates data intended to be processed
+ as an IEEE 754 single-precision float, whereas "float64"
+ indicates data intended to be processed as an IEEE 754 double-
+ precision float. Tools that generate code from JTD schemas
+ will likely produce different code for "float32" than for
+ "float64".
+
+ If _T_ starts with "int" or "uint", then the instance is accepted if
+ and only if it is a JSON number encoding a value with zero fractional
+ part. Depending on the value of _T_, this encoded number must
+ additionally fall within a particular range:
+
+ +========+===========================+===========================+
+ | _"T"_ | Minimum Value (Inclusive) | Maximum Value (Inclusive) |
+ +========+===========================+===========================+
+ | 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 |
+ +--------+---------------------------+---------------------------+
+
+ Table 2: Ranges for Integer Types
+
+ Note that
+
+ 10
+
+ and
+
+ 10.0
+
+ and
+
+ 1.0e1
+
+ encode values with zero fractional part, whereas
+
+ 10.5
+
+ encodes a number with a non-zero fractional part. Thus, the schema
+
+ {"type": "int8"}
+
+ accepts
+
+ 10
+
+ and
+
+ 10.0
+
+ and
+
+ 1.0e1
+
+ but rejects
+
+ 10.5
+
+ as well as
+
+ false
+
+ because "false" is not a number at all.
+
+ If the instance is not accepted, then the error indicator for this
+ case shall have an "instancePath" pointing to the instance and a
+ "schemaPath" pointing to the schema member with the name "type".
+
+ For example, the schema
+
+ {"type": "boolean"}
+
+ accepts
+
+ false
+
+ but rejects
+
+ 127
+
+ The schema
+
+ {"type": "float32"}
+
+ accepts
+
+ 10.5
+
+ and
+
+ 127
+
+ but rejects
+
+ false
+
+ The schema
+
+ {"type": "string"}
+
+ accepts
+
+ "1985-04-12T23:20:50.52Z"
+
+ and
+
+ "foo"
+
+ but rejects
+
+ false
+
+ The schema
+
+ {"type": "timestamp"}
+
+ accepts
+
+ "1985-04-12T23:20:50.52Z"
+
+ but rejects
+
+ "foo"
+
+ and
+
+ false
+
+ The schema
+
+ {"type": "boolean", "nullable": true}
+
+ accepts
+
+ null
+
+ and
+
+ false
+
+ but rejects
+
+ 127
+
+ In all of the examples of rejected instances given in this section,
+ the error indicator to produce is:
+
+ [{ "instancePath": "", "schemaPath": "/type" }]
+
+3.3.4. Enum
+
+ The "enum" form is meant to describe instances whose value must be
+ one of a given set of string values. The syntax of the "enum" form
+ is described in Section 2.2.4.
+
+ If a schema is of the "enum" form, then:
+
+ * If the schema has a member named "nullable" whose value is the
+ boolean "true", and the instance is the JSON primitive value
+ "null", then the schema accepts the instance.
+
+ Otherwise:
+
+ Let _E_ be the value of the schema member with the name "enum".
+ The instance is accepted if and only if it is equal to one of
+ the elements of _E_.
+
+ If the instance is not accepted, then the error indicator for this
+ case shall have an "instancePath" pointing to the instance and a
+ "schemaPath" pointing to the schema member with the name "enum".
+
+ For example, the schema
+
+ { "enum": ["PENDING", "DONE", "CANCELED"] }
+
+ accepts
+
+ "PENDING"
+
+ and
+
+ "DONE"
+
+ and
+
+ "CANCELED"
+
+ but rejects all of
+
+ 0
+
+ and
+
+ 1
+
+ and
+
+ 2
+
+ and
+
+ "UNKNOWN"
+
+ and
+
+ null
+
+ with the error indicator
+
+ [{ "instancePath": "", "schemaPath": "/enum" }]
+
+ The schema
+
+ { "enum": ["PENDING", "DONE", "CANCELED"], "nullable": true }
+
+ accepts
+
+ "PENDING"
+
+ and
+
+ null
+
+ but rejects
+
+ 1
+
+ and
+
+ "UNKNOWN"
+
+ with the error indicator
+
+ [{ "instancePath": "", "schemaPath": "/enum" }]
+
+3.3.5. Elements
+
+ The "elements" form is meant to describe instances that must be
+ arrays. A further subschema describes the elements of the array.
+ The syntax of the "elements" form is described in Section 2.2.5.
+
+ If a schema is of the "elements" form, then:
+
+ * If the schema has a member named "nullable" whose value is the
+ boolean "true", and the instance is the JSON primitive value
+ "null", then the schema accepts the instance.
+
+ Otherwise:
+
+ Let _S_ be the value of the schema member with the name
+ "elements". The instance is accepted if and only if all of the
+ following are true:
+
+ o The instance is an array. Otherwise, the error indicator
+ for this case shall have an "instancePath" pointing to the
+ instance and a "schemaPath" pointing to the schema member
+ with the name "elements".
+
+ o If the instance is an array, then every element of the
+ instance must be accepted by _S_. Otherwise, the error
+ indicators for this case are the union of all the errors
+ arising from evaluating _S_ against elements of the
+ instance.
+
+ For example, the schema
+
+ {
+ "elements": {
+ "type": "float32"
+ }
+ }
+
+ accepts
+
+ []
+
+ and
+
+ [1, 2, 3]
+
+ but rejects
+
+ null
+
+ with the error indicator
+
+ [{ "instancePath": "", "schemaPath": "/elements" }]
+
+ and rejects
+
+ [1, 2, "foo", 3, "bar"]
+
+ with the error indicators
+
+ [
+ { "instancePath": "/2", "schemaPath": "/elements/type" },
+ { "instancePath": "/4", "schemaPath": "/elements/type" }
+ ]
+
+ The schema
+
+ {
+ "elements": {
+ "type": "float32"
+ },
+ "nullable": true
+ }
+
+ accepts
+
+ null
+
+ and
+
+ []
+
+ and
+
+ [1, 2, 3]
+
+ but rejects
+
+ [1, 2, "foo", 3, "bar"]
+
+ with the error indicators
+
+ [
+ { "instancePath": "/2", "schemaPath": "/elements/type" },
+ { "instancePath": "/4", "schemaPath": "/elements/type" }
+ ]
+
+3.3.6. Properties
+
+ The "properties" form is meant to describe JSON objects being used as
+ a "struct". The syntax of the "properties" form is described in
+ Section 2.2.6.
+
+ If a schema is of the "properties" form, then:
+
+ * If the schema has a member named "nullable" whose value is the
+ boolean "true", and the instance is the JSON primitive value
+ "null", then the schema accepts the instance.
+
+ Otherwise:
+
+ - The instance must be an object.
+
+ Otherwise, the schema rejects the instance. The error
+ indicator for this case shall have an "instancePath" pointing
+ to the instance, and a "schemaPath" pointing to the schema
+ member with the name "properties" if such a schema member
+ exists; if such a member doesn't exist, "schemaPath" shall
+ point to the schema member with the name "optionalProperties".
+
+ - If the instance is an object, and the schema has a member named
+ "properties", then let _P_ be the value of the schema member
+ named "properties". Per Section 2.2.6, we know _P_ is an
+ object. For every member name in _P_, a member of the same
+ name in the instance must exist.
+
+ Otherwise, the schema rejects the instance. The error
+ indicator for this case shall have an "instancePath" pointing
+ to the instance, and a "schemaPath" pointing to the member of
+ _P_ failing the requirement just described.
+
+ - If the instance is an object, then let _P_ be the value of the
+ schema member named "properties" (if it exists) and _O_ be the
+ value of the schema member named "optionalProperties" (if it
+ exists).
+
+ For every member _I_ of the instance, find a member with the
+ same name as _I_'s in _P_ or _O_. Per Section 2.2.6, we know it
+ is not possible for both _P_ and _O_ to have such a member. If
+ the "discriminator tag exemption" is in effect on _I_ (see
+ Section 3.3.8), then ignore _I_.
+
+ Otherwise:
+
+ o If no such member in _P_ or _O_ exists and validation is not
+ in "allow additional properties" mode (see Section 3.1),
+ then the schema rejects the instance.
+
+ The error indicator for this case has an "instancePath"
+ pointing to _I_ and a "schemaPath" pointing to the schema.
+
+ o If such a member in _P_ or _O_ does exist, then call this
+ member _S_. If _S_ rejects _I_'s value, then the schema
+ rejects the instance.
+
+ The error indicators for this case are the union of the
+ error indicators from evaluating _S_ against _I_'s value.
+
+ If an instance is an object, it may have multiple errors arising
+ from the second and third bullet in the list above. In this case,
+ the error indicators are the union of the errors.
+
+ For example, the schema
+
+ {
+ "properties": {
+ "a": { "type": "string" },
+ "b": { "type": "string" }
+ },
+ "optionalProperties": {
+ "c": { "type": "string" },
+ "d": { "type": "string" }
+ }
+ }
+
+ accepts
+
+ { "a": "foo", "b": "bar" }
+
+ and
+
+ { "a": "foo", "b": "bar", "c": "baz" }
+
+ and
+
+ { "a": "foo", "b": "bar", "c": "baz", "d": "quux" }
+
+ and
+
+ { "a": "foo", "b": "bar", "d": "quux" }
+
+ but rejects
+
+ null
+
+ with the error indicator
+
+ [{ "instancePath": "", "schemaPath": "/properties" }]
+
+ and rejects
+
+ { "b": 3, "c": 3, "e": 3 }
+
+ with the error indicators
+
+ [
+ { "instancePath": "",
+ "schemaPath": "/properties/a" },
+ { "instancePath": "/b",
+ "schemaPath": "/properties/b/type" },
+ { "instancePath": "/c",
+ "schemaPath": "/optionalProperties/c/type" },
+ { "instancePath": "/e",
+ "schemaPath": "" }
+ ]
+
+ If instead the schema had "additionalProperties: true" but was
+ otherwise the same:
+
+ {
+ "properties": {
+ "a": { "type": "string" },
+ "b": { "type": "string" }
+ },
+ "optionalProperties": {
+ "c": { "type": "string" },
+ "d": { "type": "string" }
+ },
+ "additionalProperties": true
+ }
+
+ and the instance remained the same:
+
+ { "b": 3, "c": 3, "e": 3 }
+
+ then the error indicators from evaluating the instance against the
+ schema would be:
+
+ [
+ { "instancePath": "",
+ "schemaPath": "/properties/a" },
+ { "instancePath": "/b",
+ "schemaPath": "/properties/b/type" },
+ { "instancePath": "/c",
+ "schemaPath": "/optionalProperties/c/type" },
+ ]
+
+ These are the same errors as before, except the final error
+ (associated with the additional member named "e" in the instance)
+ is no longer present. This is because "additionalProperties:
+ true" enables "allow additional properties" mode on the schema.
+
+ Finally, the schema
+
+ {
+ "nullable": true,
+ "properties": {
+ "a": { "type": "string" },
+ "b": { "type": "string" }
+ },
+ "optionalProperties": {
+ "c": { "type": "string" },
+ "d": { "type": "string" }
+ },
+ "additionalProperties": true
+ }
+
+ accepts
+
+ null
+
+ but rejects
+
+ { "b": 3, "c": 3, "e": 3 }
+
+ with the error indicators
+
+ [
+ { "instancePath": "",
+ "schemaPath": "/properties/a" },
+ { "instancePath": "/b",
+ "schemaPath": "/properties/b/type" },
+ { "instancePath": "/c",
+ "schemaPath": "/optionalProperties/c/type" },
+ ]
+
+3.3.7. Values
+
+ The "values" form is meant to describe instances that are JSON
+ objects being used as an associative array. The syntax of the
+ "values" form is described in Section 2.2.7.
+
+ If a schema is of the "values" form, then:
+
+ * If the schema has a member named "nullable" whose value is the
+ boolean "true", and the instance is the JSON primitive value
+ "null", then the schema accepts the instance.
+
+ Otherwise:
+
+ Let _S_ be the value of the schema member with the name
+ "values". The instance is accepted if and only if all of the
+ following are true:
+
+ o The instance is an object. Otherwise, the error indicator
+ for this case shall have an "instancePath" pointing to the
+ instance and a "schemaPath" pointing to the schema member
+ with the name "values".
+
+ o If the instance is an object, then every member value of the
+ instance must be accepted by _S_. Otherwise, the error
+ indicators for this case are the union of all the error
+ indicators arising from evaluating _S_ against member values
+ of the instance.
+
+ For example, the schema
+
+ {
+ "values": {
+ "type": "float32"
+ }
+ }
+
+ accepts
+
+ {}
+
+ and
+
+ {"a": 1, "b": 2}
+
+ but rejects
+
+ null
+
+ with the error indicator
+
+ [{ "instancePath": "", "schemaPath": "/values" }]
+
+ and rejects
+
+ { "a": 1, "b": 2, "c": "foo", "d": 3, "e": "bar" }
+
+ with the error indicators
+
+ [
+ { "instancePath": "/c", "schemaPath": "/values/type" },
+ { "instancePath": "/e", "schemaPath": "/values/type" }
+ ]
+
+ The schema
+
+ {
+ "nullable": true,
+ "values": {
+ "type": "float32"
+ }
+ }
+
+ accepts
+
+ null
+
+ but rejects
+
+ { "a": 1, "b": 2, "c": "foo", "d": 3, "e": "bar" }
+
+ with the error indicators
+
+ [
+ { "instancePath": "/c", "schemaPath": "/values/type" },
+ { "instancePath": "/e", "schemaPath": "/values/type" }
+ ]
+
+3.3.8. Discriminator
+
+ The "discriminator" form is meant to describe JSON objects being used
+ in a fashion similar to a discriminated union construct in C-like
+ languages. The syntax of the "discriminator" form is described in
+ Section 2.2.8.
+
+ When a schema is of the "discriminator" form, it validates that:
+
+ * the instance is an object,
+
+ * the instance has a particular "tag" property,
+
+ * this "tag" property's value is a string within a set of valid
+ values, and
+
+ * the instance satisfies another schema, where this other schema is
+ chosen based on the value of the "tag" property.
+
+ The behavior of the "discriminator" form is more complex than the
+ other keywords. Readers familiar with CDDL may find the final
+ example in Appendix B helpful in understanding its behavior. What
+ follows in this section is a description of the "discriminator"
+ form's behavior, as well as some examples.
+
+ If a schema is of the "discriminator" form, then:
+
+ * Let _D_ be the schema member with the name "discriminator".
+
+ * Let _M_ be the schema member with the name "mapping".
+
+ * Let _I_ be the instance member whose name equals _D_'s value. _I_
+ may, for some rejected instances, not exist.
+
+ * Let _S_ be the member of _M_ whose name equals _I_'s value. _S_
+ may, for some rejected instances, not exist.
+
+ If the schema has a member named "nullable" whose value is the
+ boolean "true", and the instance is the JSON primitive value "null",
+ then the schema accepts the instance. Otherwise, the instance is
+ accepted if and only if all of the following are true:
+
+ * The instance is an object.
+
+ Otherwise, the error indicator for this case shall have an
+ "instancePath" pointing to the instance and a "schemaPath"
+ pointing to _D_.
+
+ * If the instance is a JSON object, then _I_ must exist.
+
+ Otherwise, the error indicator for this case shall have an
+ "instancePath" pointing to the instance and a "schemaPath"
+ pointing to _D_.
+
+ * If the instance is a JSON object and _I_ exists, _I_'s value must
+ be a string.
+
+ Otherwise, the error indicator for this case shall have an
+ "instancePath" pointing to _I_ and a "schemaPath" pointing to _D_.
+
+ * If the instance is a JSON object and _I_ exists and has a string
+ value, then _S_ must exist.
+
+ Otherwise, the error indicator for this case shall have an
+ "instancePath" pointing to _I_ and a "schemaPath" pointing to _M_.
+
+ * If the instance is a JSON object, _I_ exists, and _S_ exists, then
+ the instance must satisfy _S_'s value. Per Section 2, we know
+ _S_'s value is a schema of the "properties" form. Apply the
+ "discriminator tag exemption" afforded in Section 3.3.6 to _I_
+ when evaluating whether the instance satisfies _S_'s value.
+
+ Otherwise, the error indicators for this case shall be error
+ indicators from evaluating _S_'s value against the instance, with
+ the "discriminator tag exemption" applied to _I_.
+
+ The list items above are defined in a mutually exclusive way. For
+ any given instance and schema, exactly one of the list items above
+ will apply.
+
+ For example, the schema
+
+ {
+ "discriminator": "version",
+ "mapping": {
+ "v1": {
+ "properties": {
+ "a": { "type": "float32" }
+ }
+ },
+ "v2": {
+ "properties": {
+ "a": { "type": "string" }
+ }
+ }
+ }
+ }
+
+ rejects
+
+ null
+
+ with the error indicator
+
+ [{ "instancePath": "", "schemaPath": "/discriminator" }]
+
+ (This is the case of the instance not being an object.)
+
+ Also rejected is
+
+ {}
+
+ with the error indicator
+
+ [{ "instancePath": "", "schemaPath": "/discriminator" }]
+
+ (This is the case of _I_ not existing.)
+
+ Also rejected is
+
+ { "version": 1 }
+
+ with the error indicator
+
+ [
+ {
+ "instancePath": "/version",
+ "schemaPath": "/discriminator"
+ }
+ ]
+
+ (This is the case of _I_ existing but not having a string value.)
+
+ Also rejected is
+
+ { "version": "v3" }
+
+ with the error indicator
+
+ [
+ {
+ "instancePath": "/version",
+ "schemaPath": "/mapping"
+ }
+ ]
+
+ (This is the case of _I_ existing and having a string value but _S_
+ not existing.)
+
+ Also rejected is
+
+ { "version": "v2", "a": 3 }
+
+ with the error indicator
+
+ [
+ {
+ "instancePath": "/a",
+ "schemaPath": "/mapping/v2/properties/a/type"
+ }
+ ]
+
+ (This is the case of _I_ and _S_ existing but the instance not
+ satisfying _S_'s value.)
+
+ Finally, the schema accepts
+
+ { "version": "v2", "a": "foo" }
+
+ This instance is accepted even though "version" is not mentioned by
+ "/mapping/v2/properties"; the "discriminator tag exemption" ensures
+ that "version" is not treated as an additional property when
+ evaluating the instance against _S_'s value.
+
+ By contrast, consider the same schema but with "nullable" being
+ "true". The schema
+
+ {
+ "nullable": true,
+ "discriminator": "version",
+ "mapping": {
+ "v1": {
+ "properties": {
+ "a": { "type": "float32" }
+ }
+ },
+ "v2": {
+ "properties": {
+ "a": { "type": "string" }
+ }
+ }
+ }
+ }
+
+ accepts
+
+ null
+
+ To further illustrate the "discriminator" form with examples, recall
+ the JTD schema in Section 2.2.8, reproduced here:
+
+ {
+ "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" }
+ }
+ }
+ }
+ }
+
+ This schema accepts
+
+ { "event_type": "account_deleted", "account_id": "abc-123" }
+
+ and
+
+ {
+ "event_type": "account_payment_plan_changed",
+ "account_id": "abc-123",
+ "payment_plan": "PAID"
+ }
+
+ and
+
+ {
+ "event_type": "account_payment_plan_changed",
+ "account_id": "abc-123",
+ "payment_plan": "PAID",
+ "upgraded_by": "users/mkhwarizmi"
+ }
+
+ but rejects
+
+ {}
+
+ with the error indicator
+
+ [{ "instancePath": "", "schemaPath": "/discriminator" }]
+
+ and rejects
+
+ { "event_type": "some_other_event_type" }
+
+ with the error indicator
+
+ [
+ {
+ "instancePath": "/event_type",
+ "schemaPath": "/mapping"
+ }
+ ]
+
+ and rejects
+
+ { "event_type": "account_deleted" }
+
+ with the error indicator
+
+ [{
+ "instancePath": "",
+ "schemaPath": "/mapping/account_deleted/properties/account_id"
+ }]
+
+ and rejects
+
+ {
+ "event_type": "account_payment_plan_changed",
+ "account_id": "abc-123",
+ "payment_plan": "PAID",
+ "xxx": "asdf"
+ }
+
+ with the error indicator
+
+ [{
+ "instancePath": "/xxx",
+ "schemaPath": "/mapping/account_payment_plan_changed"
+ }]
+
+4. IANA Considerations
+
+ This document has no IANA actions.
+
+5. Security Considerations
+
+ Implementations of JTD will necessarily be manipulating JSON data.
+ Therefore, the security considerations of [RFC8259] are all relevant
+ here.
+
+ Implementations that evaluate user-inputted schemas SHOULD implement
+ mechanisms to detect and abort circular references that might cause a
+ naive implementation to go into an infinite loop. Without such
+ mechanisms, implementations may be vulnerable to denial-of-service
+ attacks.
+
+6. References
+
+6.1. Normative References
+
+ [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate
+ Requirement Levels", BCP 14, RFC 2119,
+ DOI 10.17487/RFC2119, March 1997,
+ <https://www.rfc-editor.org/info/rfc2119>.
+
+ [RFC3339] Klyne, G. and C. Newman, "Date and Time on the Internet:
+ Timestamps", RFC 3339, DOI 10.17487/RFC3339, July 2002,
+ <https://www.rfc-editor.org/info/rfc3339>.
+
+ [RFC4287] Nottingham, M., Ed. and R. Sayre, Ed., "The Atom
+ Syndication Format", RFC 4287, DOI 10.17487/RFC4287,
+ December 2005, <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, April 2013,
+ <https://www.rfc-editor.org/info/rfc6901>.
+
+ [RFC8174] Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC
+ 2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174,
+ May 2017, <https://www.rfc-editor.org/info/rfc8174>.
+
+ [RFC8259] Bray, T., Ed., "The JavaScript Object Notation (JSON) Data
+ Interchange Format", STD 90, RFC 8259,
+ DOI 10.17487/RFC8259, December 2017,
+ <https://www.rfc-editor.org/info/rfc8259>.
+
+ [RFC8610] Birkholz, H., Vigano, C., and C. Bormann, "Concise Data
+ Definition Language (CDDL): A Notational Convention to
+ Express Concise Binary Object Representation (CBOR) and
+ JSON Data Structures", RFC 8610, DOI 10.17487/RFC8610,
+ June 2019, <https://www.rfc-editor.org/info/rfc8610>.
+
+6.2. Informative References
+
+ [JSON-SCHEMA]
+ Wright, A., Andrews, H., Hutton, B., and G. Dennis, "JSON
+ Schema: A Media Type for Describing JSON Documents", Work
+ in Progress, Internet-Draft, draft-handrews-json-schema-
+ 02, 17 September 2019, <https://tools.ietf.org/html/draft-
+ handrews-json-schema-02>.
+
+ [OPENAPI] OpenAPI Initiative, "OpenAPI Specification", February
+ 2020, <https://spec.openapis.org/oas/v3.0.3>.
+
+ [RFC7071] Borenstein, N. and M. Kucherawy, "A Media Type for
+ Reputation Interchange", RFC 7071, DOI 10.17487/RFC7071,
+ November 2013, <https://www.rfc-editor.org/info/rfc7071>.
+
+ [RFC7493] Bray, T., Ed., "The I-JSON Message Format", RFC 7493,
+ DOI 10.17487/RFC7493, March 2015,
+ <https://www.rfc-editor.org/info/rfc7493>.
+
+Appendix A. Rationale for Omitted Features
+
+ This appendix is not normative.
+
+ This section describes possible features that are intentionally left
+ out of JSON Type Definition and justifies why these features are
+ omitted.
+
+A.1. Support for 64-Bit Numbers
+
+ This document does not allow "int64" or "uint64" as values for the
+ JTD "type" keyword (see Sections 2.2.3 and 3.3.3). Such hypothetical
+ "int64" or "uint64" types would behave like "int32" or "uint32"
+ (respectively) but with the range of values associated with 64-bit
+ instead of 32-bit integers. That is:
+
+ * "int64" would accept numbers between -(2**63) and (2**63)-1
+
+ * "uint64" would accept numbers between 0 and (2**64)-1
+
+ Users of "int64" and "uint64" would likely expect that the full range
+ of signed or unsigned 64-bit integers could interoperably be
+ transmitted as JSON without loss of precision. But this assumption
+ is likely to be incorrect, for the reasons given in Section 2.2 of
+ [RFC7493].
+
+ "int64" and "uint64" likely would have led users to falsely assume
+ that the full range of 64-bit integers can be interoperably processed
+ as JSON without loss of precision. To avoid leading users astray,
+ JTD omits "int64" and "uint64".
+
+A.2. Support for Non-root Definitions
+
+ This document disallows the "definitions" keyword from appearing
+ outside of root schemas (see Figure 1). Conceivably, this document
+ could have instead allowed "definitions" to appear on any schema,
+ even non-root ones. Under this alternative design, "ref"s would
+ resolve to a definition in the "nearest" (i.e., most nested) schema
+ that both contained the "ref" and had a suitably named "definitions"
+ member.
+
+ For instance, under this alternative approach, one could define
+ schemas like the one in Figure 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"
+ }
+ }
+ }
+
+ Figure 3: A Hypothetical Schema Had This Document Permitted Non-root
+ Definitions. This Is Not a Correct JTD Schema.
+
+ If schemas like that in Figure 3 were permitted, code generation from
+ JTD schemas would be more difficult, and the generated code would be
+ less useful.
+
+ Code generation would be more difficult because it would force code
+ generators to implement a name-mangling scheme for types generated
+ from definitions. This additional difficulty is not immense, but it
+ adds complexity to an otherwise relatively trivial task.
+
+ Generated code would be less useful because generated, mangled struct
+ names are less pithy than human-defined struct names. For instance,
+ the "user" definitions in Figure 3 might have been generated into
+ types named "PropertiesFooUser", "PropertiesBarUser", and
+ "PropertiesBazUser"; obtuse names like these are less useful to
+ human-written code than names like "User".
+
+ Furthermore, even though "PropertiesFooUser" and "PropertiesBarUser"
+ would be essentially identical, they would not be interchangeable in
+ many statically typed programming languages. A code generator could
+ attempt to circumvent this by deduplicating identical definitions,
+ but then the user might be confused as to why the subtly distinct
+ "PropertiesBazUser", defined from a schema allowing a property named
+ "userId" (not "user_id"), was not deduplicated.
+
+ Because there seem to be implementation and usability challenges
+ associated with non-root definitions, and because it would be easier
+ to later amend JTD to permit for non-root definitions than to later
+ amend JTD to prohibit them, this document does not permit non-root
+ definitions in JTD schemas.
+
+Appendix B. Comparison with CDDL
+
+ This appendix is not normative.
+
+ To aid the reader familiar with CDDL, this section illustrates how
+ JTD works by presenting JTD schemas and CDDL schemas that accept and
+ reject the same instances.
+
+ The JTD schema
+
+ {}
+
+ accepts the same instances as the CDDL rule
+
+ root = any
+
+ The JTD schema
+
+ {
+ "definitions": {
+ "a": { "elements": { "ref": "b" }},
+ "b": { "type": "float32" }
+ },
+ "elements": {
+ "ref": "a"
+ }
+ }
+
+ accepts the same instances as the CDDL rule
+
+ root = [* a]
+ a = [* b]
+ b = number
+
+ The JTD schema
+
+ { "enum": ["PENDING", "DONE", "CANCELED"]}
+
+ accepts the same instances as the CDDL rule
+
+ root = "PENDING" / "DONE" / "CANCELED"
+
+ The JTD schema
+
+ {"type": "boolean"}
+
+ accepts the same instances as the CDDL rule
+
+ root = bool
+
+ The JTD schemas:
+
+ {"type": "float32"}
+
+ and
+
+ {"type": "float64"}
+
+ both accept the same instances as the CDDL rule
+
+ root = number
+
+ The JTD schema
+
+ {"type": "string"}
+
+ accepts the same instances as the CDDL rule
+
+ root = tstr
+
+ The JTD schema
+
+ {"type": "timestamp"}
+
+ accepts the same instances as the CDDL rule
+
+ root = tdate
+
+ The JTD schema
+
+ { "elements": { "type": "float32" }}
+
+ accepts the same instances as the CDDL rule
+
+ root = [* number]
+
+ The JTD schema
+
+ {
+ "properties": {
+ "a": { "type": "boolean" },
+ "b": { "type": "float32" }
+ },
+ "optionalProperties": {
+ "c": { "type": "string" },
+ "d": { "type": "timestamp" }
+ }
+ }
+
+ accepts the same instances as the CDDL rule
+
+ root = { a: bool, b: number, ? c: tstr, ? d: tdate }
+
+ The JTD schema
+
+ { "values": { "type": "float32" }}
+
+ accepts the same instances as the CDDL rule
+
+ root = { * tstr => number }
+
+ Finally, the JTD schema
+
+ {
+ "discriminator": "a",
+ "mapping": {
+ "foo": {
+ "properties": {
+ "b": { "type": "float32" }
+ }
+ },
+ "bar": {
+ "properties": {
+ "b": { "type": "string" }
+ }
+ }
+ }
+ }
+
+ accepts the same instances as the CDDL rule
+
+ root = { a: "foo", b: number } / { a: "bar", b: tstr }
+
+Appendix C. Example
+
+ This appendix is not normative.
+
+ As a demonstration of JTD, in Figure 4 is a JTD schema closely
+ equivalent to the plain-English definition "reputation-object"
+ described in Section 6.2.2 of [RFC7071]:
+
+ {
+ "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" }
+ }
+ }
+ }
+ }
+ }
+
+ Figure 4: A JTD Schema Describing "reputation-object" from
+ Section 6.2.2 of [RFC7071]
+
+ This schema does not enforce the requirement that "sample-size",
+ "generated", and "expires" be unbounded positive integers. It does
+ not express the limitation that "rating", "confidence", and "normal-
+ rating" should not have more than three decimal places of precision.
+
+ The example in Figure 4 can be compared against the equivalent
+ example in Appendix H of [RFC8610].
+
+Acknowledgments
+
+ Carsten Bormann provided lots of useful guidance and feedback on
+ JTD's design and the structure of this document.
+
+ Evgeny Poberezkin suggested the addition of "nullable" and thoroughly
+ vetted this document for mistakes and opportunities for
+ simplification.
+
+ Tim Bray suggested the current "ref" model and the addition of
+ "enum". Anders Rundgren suggested extending "type" to have more
+ support for numerical types. James Manger suggested additional
+ clarifying examples of how integer types work. Adrian Farrel
+ suggested many improvements to help make this document clearer.
+
+ Members of the IETF JSON mailing list -- in particular, Pete Cordell,
+ Phillip Hallam-Baker, Nico Williams, John Cowan, Rob Sayre, and Erik
+ Wilde -- provided lots of useful feedback.
+
+ OpenAPI's "discriminator" object [OPENAPI] inspired the
+ "discriminator" form. [JSON-SCHEMA] influenced various parts of
+ JTD's early design.
+
+Author's Address
+
+ Ulysse Carion
+ Segment.io, Inc
+ 100 California Street
+ San Francisco, CA 94111
+ United States of America
+
+ Email: ulysse@segment.com