| RFC 8927 | JSON 类型定义 | 2020 年 11 月 |
| Carion | 实验性 | [页] |
本文档提出了一种称为 JSON 类型定义 (JTD) 的格式, 用于描述 JavaScript 对象表示法 (JSON) 消息的形状。 其主要目标是支持从模式生成代码,以及通过标准化的错误 指示器进行可移植验证。为此,JTD 被有意限制为不超过 主流编程语言类型系统的表达能力。这种有意的限制,以及 让 JTD 模式成为 JSON 文档的决定,使得基于 JTD 构建工具 更加容易。¶
本文档没有 IETF 共识,在此发布是为了促进对 JTD 概念的 实验。¶
本文档不是互联网标准跟踪规范;它是为审查、 实验性实现和评估而发布的。¶
本文档为互联网社区定义了一项实验性协议。 这是对 RFC 系列的贡献,独立于任何其他 RFC 流。 RFC 编辑器已自行决定发布本文档,并且不对其实现或部署价值 作出任何声明。经 RFC 编辑器批准发布的文档不是任何级别 互联网标准的候选;见 RFC 7841 第 2 节。¶
关于本文档当前状态、任何勘误以及如何提供反馈的信息,可通过 https://www.rfc-editor.org/info/rfc8927 获得。¶
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.¶
本文档描述了一种用于 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 等价形式。¶
本文档中的关键词 "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 文档中的一个值。¶
JTD 是一项实验。参与此实验包括 使用 JTD 验证或记录交换的 JSON 消息,或 在 JTD 之上构建工具。关于此实验结果的反馈 可以通过电子邮件发送给作者。预计参与此 实验的主要是提供或消费 基于 JSON 的 API 的节点。¶
如果节点正在针对 JTD 模式验证 JSON 消息, 或者依赖另一个节点这样做,则节点知道自己正在参与 该实验。如果节点正在运行从 JTD 模式生成的代码, 它们也在参与该实验。¶
该实验“逃逸”的风险表现为:一个支持 JTD 的节点 期望另一个不具备这种支持的节点 针对某个 JTD 模式验证消息。在这种情况下, 结果很可能是这些节点无法正确交换信息。¶
当 JTD 已由多个独立方实现,并且这些方 成功使用 JTD 在其内部系统内或独立方 运营的系统之间促进信息交换时, 该实验将被视为成功。¶
如果该实验被视为成功,并且 JTD 被认定为 一种有价值且受欢迎的方法,则可以将其提交给 IETF 进行进一步讨论和修订。该讨论和修订的一个可能 结果是,某个工作组产生 JTD 的 标准跟踪规范。¶
JTD 的一些实现,以及代码生成器和其他 与 JTD 相关的工具,可在以下位置获得: <https://github.com/jsontypedef>。¶
本节描述 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,
)
本节余下部分将描述无法用 CDDL 表达的 JTD 模式约束。它还将提供有效和无效 JTD 模式的 示例。¶
图 1 中的 "root-schema" 规则 允许名为 "definitions" 的成员, 但 "schema" 规则不允许这种成员。这 意味着只有根(即“顶层”)JTD 模式可以具有 "definitions" 对象,而子模式不可以。¶
因此,¶
{ "definitions": {} }
¶
是一个正确的 JTD 模式,但¶
{
"definitions": {
"foo": {
"definitions": {}
}
}
}
¶
则不是,因为子模式(例如位于 "/definitions/foo" 的对象)不得具有名为 "definitions" 的成员。¶
JTD 模式(即满足图 1 中 "schema" CDDL 规则的 JSON 对象)必须采用 八种形式之一。这些形式被定义为彼此 互斥;一个模式不能同时满足多种形式。¶
"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"。¶
"ref" 形式由 图 1 中的 "ref" CDDL 规则定义。 "ref" 形式的语义在第 3.3.2 节中描述。¶
要使 "ref" 形式的模式正确,名为 "ref" 的成员的值必须引用它所在模式的根级别 上找到的某个定义。更正式地说,对于 "ref" 形式的模式 S:¶
如果该模式是正确的,则 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" 的成员。¶
"type" 形式由 图 1 中的 "type" CDDL 规则定义。 "type" 形式的语义在第 3.3.3 节中描述。¶
作为 "type" 形式的正确 JTD 模式的一个示例,¶
{ "type": "uint8" }
¶
是正确的 JTD 模式,而¶
{ "type": true }
¶
以及¶
{ "type": "foo" }
¶
不是正确的模式,因为 "true" 和 JSON 字符串 "foo" 都不在 图 1 中 "type" CDDL 规则所描述的 "type" 成员允许值列表中。¶
"enum" 形式由 图 1 中的 "enum" CDDL 规则定义。 "enum" 形式的语义在第 3.3.4 节中描述。¶
要使 "enum" 形式的模式正确,名为 "enum" 的成员的值必须是非空字符串数组, 并且该数组不得包含重复值。更正式地说,对于 "enum" 形式的模式 S:¶
如果模式是正确的,则 E 中 MUST 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 模式的一个示例。¶
"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 模式。¶
"properties" 形式由 图 1 中的 "properties" CDDL 规则定义。"properties" 形式的 语义在第 3.3.6 节中描述。¶
要使 "properties" 形式的模式正确, 属性必须是必需的(即在 "properties" 中)或 可选的(即在 "optionalProperties" 中),但不能同时为二者。¶
更正式地说,如果一个模式同时具有名为 "properties" 的成员(其值为 P)和另一个名为 "optionalProperties" 的成员(其值为 O),则 O 和 P 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 模式语法的 递归性质。¶
"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 模式。¶
"discriminator" 形式由 图 1 中的 "discriminator" CDDL 规则定义。"discriminator" 形式的 语义在第 3.3.8 节中描述。 理解 "discriminator" 形式的语义,可能有助于读者 理解为什么本节为 "discriminator" 形式提供了 超出图 1所列内容的约束。¶
为防止带标签联合的 "discriminator" 属性上出现 歧义或不可满足的约束,"discriminator" 形式的模式 存在一个附加约束。对于 "discriminator" 形式的 模式:¶
如果该模式是正确的,则 M 的所有成员值 S 都将是 "properties" 形式的模式。对于每个 S:¶
因此,¶
{
"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 节提供 此模式接受和拒绝内容的示例。¶
本文档没有描述用于 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。¶
本节描述实例何时对正确的 JTD 模式有效,以及当实例无效时要生成的错误 指示器。¶
对于实例中“未指定”的成员,用户会有不同的期望行为。 例如,考虑图 2 中的 JTD 模式:¶
{ "properties": { "a": { "type": "string" }}}
有些用户可能期望¶
{"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" 的成员。¶
为便于一致地处理验证错误,本文档 规定了一种标准错误指示器格式。实现 SHOULD 支持以这种标准形式生成错误指示器。¶
标准错误指示器格式是一个 JSON 数组。 该数组中元素的顺序未指定。该数组的元素是 具有以下成员的 JSON 对象:¶
本节针对八种 JTD 模式形式中的每一种,描述 决定实例是否被接受的规则,以及当实例无效时要生成的错误 指示器。¶
"empty" 形式旨在描述其值 未知、不可预测,或不受模式以其他方式约束的实例。 "empty" 形式的语法在第 2.2.1 节中描述。¶
如果模式属于 "empty" 形式,则它接受所有 实例。"empty" 形式的模式永远不会生成任何错误 指示器。¶
"ref" 形式用于根据根模式的 "definitions" 中的某个内容来定义模式。"ref" 形式使模式较少重复,并且也支持 描述递归结构。"ref" 形式的语法 在第 2.2.2 节中描述。¶
如果模式属于 "ref" 形式,则:¶
当且仅当 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" 是正确的,尽管没有效果。¶
"type" 形式旨在描述其值 为布尔值、数字、字符串或时间戳 [RFC3339] 的实例。"type" 形式的语法 在第 2.2.3 节中描述。¶
如果模式属于 "type" 形式,则:¶
如果该模式具有名为 "nullable" 且其值 为布尔值 "true" 的成员,并且该实例是 JSON 原始值 "null",则该模式接受该 实例。¶
否则:¶
| 如果 "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 的值, 这个编码数字还必须落在特定范围内:¶
| "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" }]
¶
"enum" 形式旨在描述其值 必须是给定一组字符串值之一的实例。 "enum" 形式的语法在第 2.2.4 节中描述。¶
如果模式属于 "enum" 形式,则:¶
如果该模式具有名为 "nullable" 且其值 为布尔值 "true" 的成员,并且该实例是 JSON 原始值 "null",则该模式接受该 实例。¶
否则:¶
如果实例未被接受,则此情况下的错误指示器 应具有指向该实例的 "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" }]
¶
"elements" 形式旨在描述必须是数组的实例。 另一个子模式描述该数组的元素。 "elements" 形式的语法在 第 2.2.5 节中描述。¶
如果模式属于 "elements" 形式,则:¶
例如,模式¶
{
"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" }
]
¶
"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, 在 P 或 O 中查找与 I 同名的 成员。根据第 2.2.6 节,我们知道 P 和 O 不可能同时具有这样的成员。如果 I 上生效了“判别器标签豁免”(参见第 3.3.8 节),则 忽略 I。¶
否则:¶
如果实例是对象,它可能具有多个错误,这些错误 来自上面列表中的第二项和第三项。在这种情况下, 错误指示器是这些错误的并集。¶
例如,模式¶
{
"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" },
]
¶
"values" 形式旨在描述被用作 关联数组的 JSON 对象实例。"values" 形式的语法 在第 2.2.7 节中描述。¶
如果模式属于 "values" 形式,则:¶
例如,模式¶
{
"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" }
]
¶
"discriminator" 形式旨在描述以 类似 C 类语言中的判别联合构造的方式 使用的 JSON 对象。"discriminator" 形式的语法 在第 2.2.8 节中描述。¶
当模式属于 "discriminator" 形式时,它验证:¶
"discriminator" 形式的行为比其他关键词更复杂。 熟悉 CDDL 的读者可能会发现 附录 B 中的最后一个 示例有助于理解其行为。本节以下内容 是对 "discriminator" 形式行为的描述,以及 一些示例。¶
如果模式属于 "discriminator" 形式,则:¶
如果模式具有名为 "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"
}
]
¶
(这是 I 和 S 存在,但 实例不满足 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"
}]
¶
JTD 的实现必然会操作 JSON 数据。因此,[RFC8259] 的安全考虑事项在此都相关。¶
对用户输入的模式进行求值的实现 SHOULD 实现 用于检测并中止循环引用的机制,否则这些循环引用可能导致 朴素实现进入无限循环。没有此类 机制,实现可能容易受到拒绝服务 攻击。¶
本附录不是规范性的。¶
本节描述有意从 JSON 类型定义中排除的可能特性, 并说明为什么省略这些特性。¶
本文档不允许将 "int64" 或 "uint64" 作为 JTD "type" 关键词的值(参见第 2.2.3 和 3.3.3 节)。这种假设的 "int64" 或 "uint64" 类型会像 "int32" 或 "uint32"(分别)一样工作,但其取值范围 与 64 位整数而非 32 位整数相关。也就是说:¶
"int64" 和 "uint64" 的用户可能会期望 有符号或无符号 64 位整数的完整范围可以 作为 JSON 以可互操作方式传输且不会损失精度。但 由于 [RFC7493] 的第 2.2 节 中给出的原因, 这一假设很可能是不正确的。¶
"int64" 和 "uint64" 很可能会导致用户 错误地认为 64 位整数的完整范围可以作为 JSON 以可互操作方式处理且不会损失精度。为避免 误导用户,JTD 省略了 "int64" 和 "uint64"。¶
本文档不允许 "definitions" 关键词 出现在根模式之外(见图 1)。 可以设想,本文档本来也可以 允许 "definitions" 出现在任何模式上,甚至非根 模式上。在这种替代设计下,"ref" 将解析到 “最近的”(即嵌套最深的)模式中的某个定义,该模式既 包含该 "ref",又具有适当命名的 "definitions" 成员。¶
{
"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 模式生成代码 将更加困难,并且生成的代码用处更小。¶
代码生成会更加困难,因为这会迫使代码 生成器为从定义生成的类型实现名称改写方案。 这种额外困难并不巨大,但它会给本来相对简单的任务 增加复杂性。¶
生成的代码用处会更小,因为生成的、经过改写的 struct 名称不如人工定义的 struct 名称简洁。 例如,图 3 中的 "user" 定义 可能被生成 为名为 "PropertiesFooUser"、 "PropertiesBarUser" 和 "PropertiesBazUser" 的类型;像这样晦涩的 名称对人工编写的代码来说不如 "User" 这样的名称有用。¶
此外,尽管 "PropertiesFooUser" 和 "PropertiesBarUser" 本质上相同,但在许多静态类型 编程语言中它们并不可互换。代码生成器可以尝试 通过对相同定义去重来规避这一点,但这样用户可能会 困惑:为什么从允许名为 "userId"(而不是 "user_id")的属性的模式定义出的、略有不同的 "PropertiesBazUser" 没有被去重。¶
由于非根定义似乎存在与实现和可用性相关的挑战, 并且稍后修改 JTD 以允许非根定义,要比 稍后修改 JTD 以禁止它们更容易,因此本文档 不允许 JTD 模式中的非根 定义。¶
本附录不是规范性的。¶
为帮助熟悉 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 }
¶
本附录不是规范性的。¶
作为 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" }
}
}
}
}
}
此模式不会强制执行 "sample-size"、"generated" 和 "expires" 必须是 无界正整数的要求。它也不能表达 "rating"、"confidence" 和 "normal-rating" 不应 具有超过三位小数精度的限制。¶
Carsten Bormann 对 JTD 的设计和本文档结构提供了许多有用的 指导和反馈。¶
Evgeny Poberezkin 建议添加 "nullable",并且 全面审查了本文档中的错误和简化机会。¶
Tim Bray 建议采用当前的 "ref" 模型并添加 "enum"。Anders Rundgren 建议 扩展 "type" 以 更好地支持数值类型。James Manger 建议提供 更多 用于阐明整数类型如何工作的示例。Adrian Farrel 建议了 许多改进,以帮助让本文档更清晰。¶
IETF JSON 邮件列表的成员——特别是 Pete Cordell、 Phillip Hallam-Baker、Nico Williams、 John Cowan、Rob Sayre 和 Erik Wilde——提供了许多有用的 反馈。¶
OpenAPI 的 "discriminator" 对象 [OPENAPI] 启发了 "discriminator" 形式。[JSON-SCHEMA] 影响了 JTD 早期设计的 各个部分。¶