RFC 8927 JSON 类型定义 2020 年 11 月
Carion 实验性 [页]
流:
独立提交
RFC:
8927
类别:
实验性
发布:
ISSN:
2070-1721
作者:
U. Carion
Segment

RFC 8927

JSON 类型定义

摘要

本文档提出了一种称为 JSON 类型定义 (JTD) 的格式, 用于描述 JavaScript 对象表示法 (JSON) 消息的形状。 其主要目标是支持从模式生成代码,以及通过标准化的错误 指示器进行可移植验证。为此,JTD 被有意限制为不超过 主流编程语言类型系统的表达能力。这种有意的限制,以及 让 JTD 模式成为 JSON 文档的决定,使得基于 JTD 构建工具 更加容易。

本文档没有 IETF 共识,在此发布是为了促进对 JTD 概念的 实验。

本备忘录状态

本文档不是互联网标准跟踪规范;它是为审查、 实验性实现和评估而发布的。

本文档为互联网社区定义了一项实验性协议。 这是对 RFC 系列的贡献,独立于任何其他 RFC 流。 RFC 编辑器已自行决定发布本文档,并且不对其实现或部署价值 作出任何声明。经 RFC 编辑器批准发布的文档不是任何级别 互联网标准的候选;见 RFC 7841 第 2 节。

关于本文档当前状态、任何勘误以及如何提供反馈的信息,可通过 https://www.rfc-editor.org/info/rfc8927 获得。

目录

1. 引言

本文档描述了一种用于 JSON [RFC8259] 的模式语言,称为 JSON 类型定义 (JTD)。

存在许多用于描述 JSON 数据的选项。JTD 的定位是 专注于支持从模式生成代码;为此,JTD 的 表达能力被有意限制为不强于主流编程 语言类型系统所能表达的内容。

JTD 的目标是:

JTD 被有意设计为一种相当精简的模式 语言。因此,尽管 JTD 可以描述某些类别的 JSON,但它 不能描述其自身结构;本文档使用简明数据 定义语言 (CDDL) [RFC8610] 来 描述 JTD 的语法。通过使模式语言的表达能力 保持精简,JTD 使代码生成和标准化错误 指示器更易于实现。

本文档中的示例使用 C++ 编程语言中的构造。 提供这些示例是为了帮助读者理解 JTD 的原则, 但并不以任何方式构成限制。

JTD 的特性集合被设计为表示使用 JSON 的应用中的 常见模式,同时仍与广泛使用的编程语言具有明确对应关系。 因此,JTD 支持:

JTD 不支持 64 位整数的原因是 JSON 中常见模式这一原则, 因为这些整数通常以不可互操作的方式 (即忽略 第 2.2 节 of [RFC7493] 中的建议)或相互 不一致的方式通过 JSON 传输。附录 A.1 进一步 阐述了为什么 JTD 不支持 64 位 整数。

JTD 不支持例如最大到 2**53-1 的整数数据类型, 其原因是与常见编程语言保持明确对应这一原则。

预计对于许多用例而言,JTD 表达能力所提供的 模式语言已经足够。如果需要表达能力更强的语言, CDDL 和其他语言中存在替代方案。

本文档没有 IETF 共识,在此发布是为了 促进对 JTD 概念的实验。该实验的目的 是获得使用 JTD 的经验,并可能相应修订这项 工作。如果认定 JTD 是一种有价值且受欢迎的 方法,则可以将其提交给 IETF 进行进一步讨论和 修订。

本文档具有以下结构。 第 2 节 定义 JTD 的语法。 第 3 节 描述 JTD 的语义; 这包括确定某些数据是否满足模式,以及当数据 不满足时应产生哪些错误指示器。附录 A 讨论为什么某些特性从 JTD 中被省略。附录 B 给出了各种 JTD 模式及其 CDDL 等价形式。

1.1. 术语

本文档中的关键词 "MUST"、"MUST NOT"、 "REQUIRED"、"SHALL"、"SHALL NOT"、"SHOULD"、"SHOULD NOT"、 "RECOMMENDED"、"NOT RECOMMENDED"、 "MAY" 和 "OPTIONAL",当且仅当 它们以全大写形式出现时,应按 BCP 14 [RFC2119] [RFC8174] 中的描述进行解释,如这里所示。

本文档中出现的术语 "JSON Pointer" 应按 [RFC6901] 中的定义理解。

本文档中的术语 "object"、"member"、"array"、"number"、"name" 和 "string" 应按 [RFC8259] 中的描述进行解释。

本文档中出现的术语 "instance" 指的是 针对 JTD 模式进行验证的 JSON 值。该值可以是 整个 JSON 文档,也可以是嵌入在 JSON 文档中的一个值。

1.2. 实验范围

JTD 是一项实验。参与此实验包括 使用 JTD 验证或记录交换的 JSON 消息,或 在 JTD 之上构建工具。关于此实验结果的反馈 可以通过电子邮件发送给作者。预计参与此 实验的主要是提供或消费 基于 JSON 的 API 的节点。

如果节点正在针对 JTD 模式验证 JSON 消息, 或者依赖另一个节点这样做,则节点知道自己正在参与 该实验。如果节点正在运行从 JTD 模式生成的代码, 它们也在参与该实验。

该实验“逃逸”的风险表现为:一个支持 JTD 的节点 期望另一个不具备这种支持的节点 针对某个 JTD 模式验证消息。在这种情况下, 结果很可能是这些节点无法正确交换信息。

当 JTD 已由多个独立方实现,并且这些方 成功使用 JTD 在其内部系统内或独立方 运营的系统之间促进信息交换时, 该实验将被视为成功。

如果该实验被视为成功,并且 JTD 被认定为 一种有价值且受欢迎的方法,则可以将其提交给 IETF 进行进一步讨论和修订。该讨论和修订的一个可能 结果是,某个工作组产生 JTD 的 标准跟踪规范。

JTD 的一些实现,以及代码生成器和其他 与 JTD 相关的工具,可在以下位置获得: <https://github.com/jsontypedef>

2. 语法

本节描述 JSON 文档何时是一个正确的 JTD 模式。由于简明数据定义语言 (CDDL) 非常适合 定义复杂 JSON 格式(例如 JTD 模式)的任务,因此本 节使用 CDDL 来描述 JTD 模式的格式。

JTD 模式可以递归地包含其他模式。在本文档中, “根模式”是不包含在另一个模式中的模式, 即它是“顶层”的。

JTD 模式是采用适当形式的 JSON 对象。JTD 模式可以包含“附加数据”,见第 2.3 节。根 JTD 模式可以 可选地包含定义(从名称到模式的映射)。

正确的根 JTD 模式 MUST 匹配本节中描述的 "root-schema" CDDL 规则。正确的非根 JTD 模式 MUST 匹配本节中描述的 "schema" CDDL 规则。

; root-schema 与 schema 相同,但另外允许
; definitions。
;
; 禁止 definitions 出现在非根模式上。
root-schema = {
  ? definitions: { * tstr => { schema}},
  schema,
}
; schema 是定义 JTD 模式的主要 CDDL 规则。
;
; 所有 JTD 模式都是采用此处列出的八种形式之一的
; JSON 对象。
schema = (
  ref //
  type //
  enum //
  elements //
  properties //
  values //
  discriminator //
  empty //
)
; shared 是包含全部八种模式形式所共有属性的
; CDDL 规则。
shared = (
  ? metadata: { * tstr => any },
  ? nullable: bool,
)
; empty 描述 "empty" 模式形式。
empty = shared
; ref 描述 "ref" 模式形式。
;
; 此形式还有无法用 CDDL 表达的附加约束。
; 第 2.2.2 节详细描述了这些附加约束。
ref = ( ref: tstr, shared )
; type 描述 "type" 模式形式。
type = (
  type: "boolean"
    / "float32"
    / "float64"
    / "int8"
    / "uint8"
    / "int16"
    / "uint16"
    / "int32"
    / "uint32"
    / "string"
    / "timestamp",
  shared,
)
; enum 描述 "enum" 模式形式。
;
; 此形式还有无法用 CDDL 表达的附加约束。
; 第 2.2.4 节详细描述了这些附加约束。
enum = ( enum: [+ tstr], shared )
; elements 描述 "elements" 模式形式。
elements = ( elements: { schema }, shared )
; properties 描述 "properties" 模式形式。
;
; 此 CDDL 规则的定义方式使得 "properties" 形式的模式
; 可以省略名为 "properties" 的成员或名为
; "optionalProperties" 的成员,但不能同时省略二者。
;
; 此形式还有无法用 CDDL 表达的附加约束。
; 第 2.2.6 节详细描述了这些附加约束。
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 描述 "values" 模式形式。
values = ( values: { schema }, shared )
; discriminator 描述 "discriminator" 模式形式。
;
; 此形式还有无法用 CDDL 表达的附加约束。
; 第 2.2.8 节详细描述了这些附加约束。
discriminator = (
  discriminator: tstr,
  ; 请特别注意:此规则是根据 "properties"
  ; CDDL 规则定义的,而不是根据 "schema" CDDL 规则。
  mapping: { * tstr => { properties } }
  shared,
)
图 1模式的 CDDL 定义

本节余下部分将描述无法用 CDDL 表达的 JTD 模式约束。它还将提供有效和无效 JTD 模式的 示例。

2.1. 根模式与非根 模式

图 1 中的 "root-schema" 规则 允许名为 "definitions" 的成员, 但 "schema" 规则不允许这种成员。这 意味着只有根(即“顶层”)JTD 模式可以具有 "definitions" 对象,而子模式不可以。

因此,

   { "definitions": {} }

是一个正确的 JTD 模式,但

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

则不是,因为子模式(例如位于 "/definitions/foo" 的对象)不得具有名为 "definitions" 的成员。

2.2. 形式

JTD 模式(即满足图 1 中 "schema" CDDL 规则的 JSON 对象)必须采用 八种形式之一。这些形式被定义为彼此 互斥;一个模式不能同时满足多种形式。

2.2.1.

"empty" 形式由图 1 中的 "empty" CDDL 规则定义。"empty" 形式的语义 在第 3.3.1 节中描述。

尽管名称为 "empty","empty" 形式的模式 并不一定是空 JSON 对象。与八种形式中任何一种 模式一样,"empty" 形式的模式可以包含名为 "nullable"(其值必须是 "true" 或 "false")或 "metadata"(其值必须是对象) 的成员,或同时包含二者。

因此,

   {}

以及

   { "nullable": true }

以及

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

都是 "empty" 形式的正确 JTD 模式,但

   { "nullable": "foo" }

则不是,因为名为 "nullable" 的成员的值 必须是 "true" 或 "false"。

2.2.2. Ref

"ref" 形式由 图 1 中的 "ref" CDDL 规则定义。 "ref" 形式的语义在第 3.3.2 节中描述。

要使 "ref" 形式的模式正确,名为 "ref" 的成员的值必须引用它所在模式的根级别 上找到的某个定义。更正式地说,对于 "ref" 形式的模式 S

  • B 为包含该模式的根模式, 如果该模式本身就是根模式,则为该模式本身。
  • RS 中名称为 "ref" 的成员的值。

如果该模式是正确的,则 B MUST 具有名为 "definitions" 的成员 D,并且 D MUST 包含名称等于 R 的成员。

因此,

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

是一个正确的 JTD 模式,并展示了 "ref" 形式的要点:避免对同一事物重复 定义两次。然而,

   { "ref": "foo" }

不是正确的 JTD 模式,因为没有顶层 "definitions",因此 "ref" 形式无法 正确。类似地,

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

也不是正确的 JTD 模式,因为顶层 "definitions" 中没有名为 "bar" 的成员。

2.2.3. 类型

"type" 形式由 图 1 中的 "type" CDDL 规则定义。 "type" 形式的语义在第 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" 形式的语义在第 3.3.4 节中描述。

要使 "enum" 形式的模式正确,名为 "enum" 的成员的值必须是非空字符串数组, 并且该数组不得包含重复值。更正式地说,对于 "enum" 形式的模式 S

  • ES 中名称为 "enum" 的成员的值。

如果模式是正确的,则 EMUST NOT 存在任何一对 编码为相等字符串值的元素,其中 字符串相等按 [RFC8259] 的第 8.3 节定义。

因此,

   { "enum": [] }

不是正确的 JTD 模式,因为名为 "enum" 的成员的值必须非空,并且

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

不是正确的 JTD 模式,因为

   "a\\b"

   "a\u005Cb"

[RFC8259] 的第 8.3 节 中给出的字符串 相等定义,编码为相等的字符串。相比之下,

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

是 "enum" 形式的正确 JTD 模式的一个示例。

2.2.5. 元素

"elements" 形式由 图 1 中的 "elements" CDDL 规则定义。"elements" 形式的 语义在第 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" 形式的 语义在第 3.3.6 节中描述。

要使 "properties" 形式的模式正确, 属性必须是必需的(即在 "properties" 中)或 可选的(即在 "optionalProperties" 中),但不能同时为二者。

更正式地说,如果一个模式同时具有名为 "properties" 的成员(其值为 P)和另一个名为 "optionalProperties" 的成员(其值为 O),则 OP MUST NOT 具有任何共同的成员名; 也就是说,在 [RFC8259] 的第 8.3 节 给出的字符串 相等定义下,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" 形式的语义 在第 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" 形式的 语义在第 3.3.8 节中描述。 理解 "discriminator" 形式的语义,可能有助于读者 理解为什么本节为 "discriminator" 形式提供了 超出图 1所列内容的约束。

为防止带标签联合的 "discriminator" 属性上出现 歧义或不可满足的约束,"discriminator" 形式的模式 存在一个附加约束。对于 "discriminator" 形式的 模式:

  • D 为该模式中名称为 "discriminator" 的成员。
  • M 为该模式中名称为 "mapping" 的成员。

如果该模式是正确的,则 M 的所有成员值 S 都将是 "properties" 形式的模式。对于每个 S

  • 如果 S 具有名称等于 "nullable" 的成员 N,则 N 的值 MUST NOT 是 JSON 原始值 "true"。
  • 对于 S 中名称等于 "properties" 或 "optionalProperties" 的每个成员 PP 的值(它必须是对象)MUST NOT 包含任何 名称等于 D 的值的成员。

因此,

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

是一个不正确的模式,因为 "mapping" 的某个成员 具有名为 "nullable" 且值为 "true" 的成员。这 会暗示实例可以为 null。然而,顶层 模式缺少这样的设为 "true" 的 "nullable", 这会暗示实例事实上不能为 null。如果这是 正确的 JTD 模式,则不清楚哪条信息 优先。

JTD 通过在语法层面禁止对 "discriminator" 形式模式所描述的实例是否 可以为 null 作出相互矛盾规范的可能性,来处理 此类潜在歧义。判别器 "mapping" 中的模式不能将 "nullable" 设为 "true";只有判别器本身 可以以这种方式使用 "nullable"。

还可由此推出,

   {
     "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 通过在语法层面禁止对 判别器 "tags" 作出相互矛盾规范的可能性,来处理 此类潜在歧义。判别器 "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 的消息系统中 常见的数据模式。第 3.3.8 节提供 此模式接受和拒绝内容的示例。

2.3. 扩展 JTD 的语法

本文档没有描述用于 JTD 模式验证的任何扩展机制;JTD 模式验证在第 3 节中描述。 然而,模式被定义为可以可选地包含 一个 "metadata" 关键词,其值为任意 JSON 对象。称此对象的成员为 "metadata members"。

用户 MAY 向 JTD 模式添加 metadata members,以 传达与验证无关的信息。例如,此类 metadata members 可以向代码生成器提供提示,或触发某个 从模式生成用户界面的库的某些 特殊行为。

用户 SHOULD NOT 期望 metadata members 能被其他方理解。因此,如果与其他方保持一致验证是 一项要求,则用户 MUST NOT 使用 metadata members 来影响 第 3 节所描述的模式验证 的工作方式。

如果以某种方式已知其他方支持这些 metadata members,则用户 MAY 期望 metadata members 能被其他方理解,并且 MAY 使用 metadata members 来影响模式验证 的工作方式。例如,双方可以通过带外方式达成一致, 支持带有影响验证的自定义 metadata member 的扩展 JTD。

3. 语义

本节描述实例何时对正确的 JTD 模式有效,以及当实例无效时要生成的错误 指示器。

3.1. 允许附加 属性

对于实例中“未指定”的成员,用户会有不同的期望行为。 例如,考虑图 2 中的 JTD 模式:

{ "properties": { "a": { "type": "string" }}}
图 2一个说明性的 JTD 模式

有些用户可能期望

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

满足图 2 中的模式。 其他人可能不同意,因为 "b" 不是该模式中 描述的属性之一。在本文档中,允许 这类“未指定”的成员(例如本例中的 "b")发生在 求值处于“允许附加属性”模式时。

默认情况下,模式求值不允许附加属性, 但可以通过让模式包含一个名为 "additionalProperties" 且其值为 "true" 的成员来覆盖此行为。

更正式地说,如果存在 S 的一个成员, 其名称等于 "additionalProperties",且其值为布尔值 "true",则模式 S 的求值处于“允许 附加属性”模式。否则,S 的求值不处于 “允许附加属性”模式。

有关允许未知属性如何影响模式求值,请参见 第 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" 的值取决于 模式的形式,并在第 3.3 节中详细描述。

3.3. 形式

本节针对八种 JTD 模式形式中的每一种,描述 决定实例是否被接受的规则,以及当实例无效时要生成的错误 指示器。

正确模式可以采用的形式在 第 2 节中作了正式描述。

3.3.1.

"empty" 形式旨在描述其值 未知、不可预测,或不受模式以其他方式约束的实例。 "empty" 形式的语法在第 2.2.1 节中描述。

如果模式属于 "empty" 形式,则它接受所有 实例。"empty" 形式的模式永远不会生成任何错误 指示器。

3.3.2. Ref

"ref" 形式用于根据根模式的 "definitions" 中的某个内容来定义模式。"ref" 形式使模式较少重复,并且也支持 描述递归结构。"ref" 形式的语法 在第 2.2.2 节中描述。

如果模式属于 "ref" 形式,则:

  • 如果该模式具有名为 "nullable" 且其值 为布尔值 "true" 的成员,并且该实例是 JSON 原始值 "null",则该模式接受该 实例。

    否则:

    • R 为名称为 "ref" 的模式成员的值。
    • B 为包含该模式的根模式, 如果该模式本身就是根模式,则为该模式本身。
    • DB 中名称为 "definitions" 的成员。根据第 2 节, 我们知道 D 存在。
    • SD 中 名称等于 R 的成员的值。根据 第 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" 形式旨在描述其值 为布尔值、数字、字符串或时间戳 [RFC3339] 的实例。"type" 形式的语法 在第 2.2.3 节中描述。

如果模式属于 "type" 形式,则:

  • 如果该模式具有名为 "nullable" 且其值 为布尔值 "true" 的成员,并且该实例是 JSON 原始值 "null",则该模式接受该 实例。

    否则:

    • T 为 名称为 "type" 的成员的值。下表根据 T 的值描述实例是否被接受:
    • 表 1类型的可接受值
      如果 "T" 等于…… 则实例在它是以下内容时 被接受……
      boolean 等于 "true" 或 "false"
      float32 JSON 数字
      float64 JSON 数字
      int8 表 2
      uint8 表 2
      int16 表 2
      uint16 表 2
      int32 表 2
      uint32 表 2
      string JSON 字符串
      timestamp 符合 [RFC3339] 中描述的标准 格式,并由 [RFC4287] 的第 3.3 节 进一步细化的 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" 形式的语法在第 2.2.4 节中描述。

如果模式属于 "enum" 形式,则:

  • 如果该模式具有名为 "nullable" 且其值 为布尔值 "true" 的成员,并且该实例是 JSON 原始值 "null",则该模式接受该 实例。

    否则:

    • E 为 名称为 "enum" 的模式成员的值。 当且仅当实例等于 E 的某个元素时,该实例被接受。

如果实例未被接受,则此情况下的错误指示器 应具有指向该实例的 "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" 形式的语法在 第 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" 形式的语法 在第 2.2.6 节中描述。

如果模式属于 "properties" 形式,则:

  • 如果该模式具有名为 "nullable" 且其值 为布尔值 "true" 的成员,并且该实例是 JSON 原始值 "null",则该模式接受该 实例。

    否则:

    • 该实例必须是对象。

      否则,模式拒绝该实例。 此情况下的错误指示器应具有指向该实例的 "instancePath",以及指向名称为 "properties" 的模式成员的 "schemaPath"(如果存在这样的 模式成员);如果不存在这样的成员,"schemaPath" 应指向名称为 "optionalProperties" 的模式成员。

    • 如果实例是对象,并且模式 具有名为 "properties" 的成员,则令 P 为 名为 "properties" 的模式成员的值。根据 第 2.2.6 节,我们知道 P 是一个对象。对于 P 中的每个成员名, 实例中必须存在同名成员。

      否则,模式拒绝该实例。 此情况下的错误指示器应具有指向该实例的 "instancePath", 以及指向 P 中未满足刚才所述要求的成员的 "schemaPath"。

    • 如果实例是对象,则令 P 为名为 "properties" 的模式成员的值 (如果存在),并令 O 为名为 "optionalProperties" 的模式成员的值(如果存在)。

      对于实例的每个成员 I, 在 PO 中查找与 I 同名的 成员。根据第 2.2.6 节,我们知道 PO 不可能同时具有这样的成员。如果 I 上生效了“判别器标签豁免”(参见第 3.3.8 节),则 忽略 I

      否则:

      • 如果 PO 中不存在这样的成员,并且 验证不处于“允许附加属性”模式(参见 第 3.1 节),则模式拒绝该实例。

        此情况下的错误指示器具有 指向 I 的 "instancePath",以及 指向该模式的 "schemaPath"。

      • 如果 PO 中确实存在这样的成员, 则称此成员为 S。如果 S 拒绝 I 的值,则模式拒绝该实例。

        此情况下的错误指示器是 针对 I 的值求值 S 所得错误指示器的并集。

  • 如果实例是对象,它可能具有多个错误,这些错误 来自上面列表中的第二项和第三项。在这种情况下, 错误指示器是这些错误的并集。

    例如,模式

       {
         "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" 在该模式上启用了“允许附加 属性”模式。

    最后,模式

       {
         "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" 形式的语法 在第 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 类语言中的判别联合构造的方式 使用的 JSON 对象。"discriminator" 形式的语法 在第 2.2.8 节中描述。

当模式属于 "discriminator" 形式时,它验证:

  • 实例是对象,
  • 实例具有特定的 "tag" 属性,
  • 该 "tag" 属性的值是在一组 有效值中的字符串,并且
  • 实例满足另一个模式,其中该另一个 模式是根据 "tag" 属性的值选择的。

"discriminator" 形式的行为比其他关键词更复杂。 熟悉 CDDL 的读者可能会发现 附录 B 中的最后一个 示例有助于理解其行为。本节以下内容 是对 "discriminator" 形式行为的描述,以及 一些示例。

如果模式属于 "discriminator" 形式,则:

  • D 为名称为 "discriminator" 的模式成员。
  • M 为名称为 "mapping" 的模式成员。
  • I 为名称等于 D 的值的实例成员。对于某些被拒绝的实例, I 可能不存在。
  • SM 中 名称等于 I 的值的成员。对于某些被拒绝的实例, 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 的 值。根据第 2 节,我们知道 S 的 值是 "properties" 形式的模式。在求值该实例是否满足 S 的值时,将第 3.3.6 节中给予的 “判别器标签豁免”应用于 I

    否则,此情况下的错误指示器应为 对实例求值 S 的值所得到的错误 指示器,并将“判别器标签豁免”应用于 I

上面的列表项以互斥方式定义。对于任何给定的 实例和模式,正好会应用上面列表项中的一项。

例如,模式

   {
     "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",该实例也会被接受;“判别器标签 豁免”确保在针对 S 的值求值该实例时, "version" 不会被视为附加属性。

相比之下,考虑相同的模式,但其中 "nullable" 为 "true"。模式

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

接受

   null

为通过示例进一步说明 "discriminator" 形式, 回顾第 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. 和 C. Newman, “互联网上的日期与时间:时间戳”, RFC 3339, DOI 10.17487/RFC3339, , <https://www.rfc-editor.org/info/rfc3339>.
[RFC4287]
Nottingham, M., 编. 和 R. Sayre, 编., “Atom 联合格式”, RFC 4287, DOI 10.17487/RFC4287, , <https://www.rfc-editor.org/info/rfc4287>.
[RFC6901]
Bryan, P., 编., Zyp, K., 和 M. Nottingham, 编., “JavaScript 对象 表示法 (JSON) 指针”, 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., 编., “JavaScript 对象表示法 (JSON) 数据交换格式”, STD 90, RFC 8259, DOI 10.17487/RFC8259, , <https://www.rfc-editor.org/info/rfc8259>.
[RFC8610]
Birkholz, H., Vigano, C., 和 C. Bormann, “简明数据定义 语言 (CDDL):用于表达简明二进制对象表示 (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., 和 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. 和 M. Kucherawy, “用于信誉交换的媒体类型”, RFC 7071, DOI 10.17487/RFC7071, , <https://www.rfc-editor.org/info/rfc7071>.
[RFC7493]
Bray, T., 编., “I-JSON 消息 格式”, RFC 7493, DOI 10.17487/RFC7493, , <https://www.rfc-editor.org/info/rfc7493>.

附录 A. 省略特性的 理由

本附录不是规范性的。

本节描述有意从 JSON 类型定义中排除的可能特性, 并说明为什么省略这些特性。

A.1. 对 64 位 数字的支持

本文档不允许将 "int64" 或 "uint64" 作为 JTD "type" 关键词的值(参见第 2.2.33.3.3 节)。这种假设的 "int64" 或 "uint64" 类型会像 "int32" 或 "uint32"(分别)一样工作,但其取值范围 与 64 位整数而非 32 位整数相关。也就是说:

  • "int64" 会接受 -(2**63) 到 (2**63)-1 之间的数字
  • "uint64" 会接受 0 到 (2**64)-1 之间的数字

"int64" 和 "uint64" 的用户可能会期望 有符号或无符号 64 位整数的完整范围可以 作为 JSON 以可互操作方式传输且不会损失精度。但 由于 [RFC7493] 的第 2.2 节 中给出的原因, 这一假设很可能是不正确的。

"int64" 和 "uint64" 很可能会导致用户 错误地认为 64 位整数的完整范围可以作为 JSON 以可互操作方式处理且不会损失精度。为避免 误导用户,JTD 省略了 "int64" 和 "uint64"。

A.2. 对非根 定义的支持

本文档不允许 "definitions" 关键词 出现在根模式之外(见图 1)。 可以设想,本文档本来也可以 允许 "definitions" 出现在任何模式上,甚至非根 模式上。在这种替代设计下,"ref" 将解析到 “最近的”(即嵌套最深的)模式中的某个定义,该模式既 包含该 "ref",又具有适当命名的 "definitions" 成员。

例如,在这种替代方法下,可以定义 如图 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" 本质上相同,但在许多静态类型 编程语言中它们并不可互换。代码生成器可以尝试 通过对相同定义去重来规避这一点,但这样用户可能会 困惑:为什么从允许名为 "userId"(而不是 "user_id")的属性的模式定义出的、略有不同的 "PropertiesBazUser" 没有被去重。

由于非根定义似乎存在与实现和可用性相关的挑战, 并且稍后修改 JTD 以允许非根定义,要比 稍后修改 JTD 以禁止它们更容易,因此本文档 不允许 JTD 模式中的非根 定义。

附录 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 }

附录 C. 示例

本附录不是规范性的。

作为 JTD 的演示,图 4 中给出了一个 JTD 模式,它与 [RFC7071] 的第 6.2.2 节 中描述的普通英文定义 "reputation-object" 非常等价:

{
  "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描述 [RFC7071] 的第 6.2.2 节 中 "reputation-object" 的 JTD 模式

此模式不会强制执行 "sample-size"、"generated" 和 "expires" 必须是 无界正整数的要求。它也不能表达 "rating"、"confidence" 和 "normal-rating" 不应 具有超过三位小数精度的限制。

图 4 中的示例可以 与 [RFC8610] 的附录 H 中的等价示例进行比较。

致谢

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 SayreErik Wilde——提供了许多有用的 反馈。

OpenAPI 的 "discriminator" 对象 [OPENAPI] 启发了 "discriminator" 形式。[JSON-SCHEMA] 影响了 JTD 早期设计的 各个部分。

作者地址

Ulysse Carion
Segment.io, Inc
100 California Street
San Francisco, CA 94111
美利坚合众国