# JSON 类型定义

¥JSON Type Definition

本文档非正式地描述了 JSON 类型定义 (JTD) 规范,以帮助 Ajv 用户开始使用它。正式定义请参见 RFC8927 (opens new window)。请报告本文档与规范中的任何矛盾。

¥This document informally describes JSON Type Definition (JTD) specification to help Ajv users to start using it. For formal definition please refer to RFC8927 (opens new window). Please report any contradictions in this document with the specification.

要使用 JTD 结构,你需要导入不同的 Ajv 类:

¥To use JTD schemas you need to import a different Ajv class:

# JTD 结构形式

¥JTD schema forms

JTD 规范定义了 JSON 结构可采用的 8 种不同形式,用于 JSON 消息(API 请求和响应)中最广泛使用的数据类型之一。

¥JTD specification defines 8 different forms that the schema for JSON can take for one of most widely used data types in JSON messages (API requests and responses).

所有形式均要求:

¥All forms require that:

  • schema 是一个具有不同成员的对象,具体取决于形式

    ¥schema is an object with different members, depending on the form

  • 每个形式可以有:

    ¥each form can have:

    • 带有布尔值的可选成员 nullable,允许数据实例为 JSON null

      ¥an optional member nullable with a boolean value that allows data instance to be JSON null.

    • 一个可选成员 metadata,其对象值允许传递任何附加信息或扩展规范(Ajv 定义可在 metadata 内部使用的关键字 "union")

      ¥an optional member metadata with an object value that allows to pass any additional information or extend the specification (Ajv defines keyword "union" that can be used inside metadata)

根结构可以具有成员 definitions,该成员具有结构字典,可以使用形式 ref 从任何其他结构进行引用

¥Root schema can have member definitions that has a dictionary of schemas that can be references from any other schemas using form ref

# 类型形式

¥Type form

primitive values

这种形式定义了一个原始值。

¥This form defines a primitive value.

它有一个必需的成员 type 和一个可选的成员 nullablemetadata,不允许有其他成员。

¥It has a required member type and an optional members nullable and metadata, no other members are allowed.

type 可以具有以下值之一:

¥type can have one of the following values:

  • "string" - 定义一个字符串

    ¥"string" - defines a string

  • "boolean" - 定义布尔值 truefalse

    ¥"boolean" - defines boolean value true or false

  • "timestamp" - 定义时间戳(接受 RFC3339 (opens new window) JSON 字符串或日期对象,可通过 timestamp Ajv 选项进行配置)

    ¥"timestamp" - defines timestamp ( accepting either an RFC3339 (opens new window) JSON string or a Date object, configurable via the timestamp Ajv option)

  • 定义整数的 type 值:

    ¥type values that define integer numbers:

    • "int8" - signed byte value (-128 .. 127)

    • "uint8" - unsigned byte value (0 .. 255)

    • "int16" - signed word value (-32768 .. 32767),

    • "uint16" - unsigned word value (0 .. 65535)

    • "int32" - 有符号 32 位整数值

      ¥"int32" - signed 32-bit integer value

    • "uint32" - 无符号 32 位整数值

      ¥"uint32" - unsigned 32-bit integer value

  • 定义浮点数的 type 值:

    ¥type values that define floating point numbers:

    • "float32" - 32 位实数

      ¥"float32" - 32-bit real number

    • "float64" - 64 位实数

      ¥"float64" - 64-bit real number

与 JSON Schema 不同,JTD 不允许定义可以采用多种类型之一的值,但可以将它们定义为 nullable

¥Unlike JSON Schema, JTD does not allow defining values that can take one of several types, but they can be defined as nullable.

示例

¥Example

{
  type: "string"
}

# 枚举形式

¥Enum form

这种形式定义了一个字符串,它可以从列表中获取一个值(列表中的值必须是唯一的)。

¥This form defines a string that can take one of the values from the list (the values in the list must be unique).

它有必需的成员 enum 和可选的成员 nullablemetadata,不允许有其他成员。

¥It has a required member enum and optional members nullable and metadata, no other members are allowed.

与 JSON Schema 不同,JTD 不允许使用字符串以外的任何其他类型的值定义 enum

¥Unlike JSON Schema, JTD does not allow defining enum with values of any other type than string.

示例

¥Example

{
  enum: ["foo", "bar"]
}

# 元素形式

¥Elements form

arrays

这种形式定义了一个任意大小(可能为空)的同质数组,其中的元素满足给定的结构。

¥This form defines a homogenous array of any size (possibly empty) with the elements that satisfy a given schema.

它有一个必需的成员 elements(元素应满足的结构)和可选的成员 nullablemetadata,不允许有其他成员。

¥It has a required member elements (schema that elements should satisfy) and optional members nullable and metadata, no other members are allowed.

与 JSON Schema 不同,数据实例必须是 JSON 数组(不使用额外的 type 关键字),并且无法强制执行大多数语言类型级别上无法存在的限制,例如数组大小和项的唯一性。

¥Unlike JSON Schema, the data instance must be JSON array (without using additional type keyword), and there is no way to enforce the restrictions that cannot be present on type level of most languages, such as array size and uniqueness of items.

示例

¥Example

结构:

¥Schema:

{
  elements: {
    type: "string"
  }
}

有效数据:[]["foo"]["foo", "bar"]

¥Valid data: [], ["foo"], ["foo", "bar"]

无效数据:["foo", 1],除数组之外的任何类型

¥Invalid data: ["foo", 1], any type other than array

# 属性形式

¥Properties form

objects

此形式定义了已定义必需属性和可选属性的记录(JSON 对象)。

¥This form defines record (JSON object) that has defined required and optional properties.

要求此形式具有 properties 成员或 optionalProperties 成员,或两者都有,在这种情况下,它们不能具有重叠属性。通过添加具有值 true 的可选布尔成员 additionalProperties 可以允许其他属性。与所有其他形式一样,此形式可以具有可选的 nullablemetadata 成员。

¥It is required that this form has either properties member, or optionalProperties, or both, in which case the cannot have overlapping properties. Additional properties can be allowed by adding an optional boolean member additionalProperties with a value true. This form, as all other, can have optional nullable and metadata members.

与 JSON Schema 不同,properties 模式成员中定义的所有属性都是必需的,数据实例必须是 JSON 对象(不使用附加 type 关键字),并且默认情况下不允许附加属性(鉴别器标记除外) - see the next section).这种严格性可以最大限度地减少用户错误。

¥Unlike JSON Schema, all properties defined in properties schema member are required, the data instance must be JSON object (without using additional type keyword) and by default additional properties are not allowed (with the exception of discriminator tag - see the next section). This strictness minimises user mistakes.

示例 1。

¥Example 1.

结构:

¥Schema:

{
  properties: {
    foo: {
      type: "string"
    }
  }
}

有效数据:{foo: "bar"}

¥Valid data: {foo: "bar"}

无效数据:{}{foo: 1}{foo: "bar", bar: 1}、除对象之外的任何类型

¥Invalid data: {}, {foo: 1}, {foo: "bar", bar: 1}, any type other than object

示例 2.

¥Example 2.

结构:

¥Schema:

{
  properties: {
    foo: {type: "string"}
  },
  optionalProperties: {
    bar: {enum: ["1", "2"]}
  },
  additionalProperties: true
}

有效数据:{foo: "bar"}{foo: "bar", bar: "1"}{foo: "bar", additional: 1}

¥Valid data: {foo: "bar"}, {foo: "bar", bar: "1"}, {foo: "bar", additional: 1}

无效数据:{}{foo: 1}{foo: "bar", bar: "3"}、除对象之外的任何类型

¥Invalid data: {}, {foo: 1}, {foo: "bar", bar: "3"}, any type other than object

Example 3:无效架构(必需和可选属性重叠)

¥Example 3: invalid schema (overlapping required and optional properties)

{
  properties: {
    foo: {type: "string"}
  },
  optionalProperties: {
    foo: {type: "string"}
  }
}

# 判别器形式

¥Discriminator form

tagged union

这种形式定义了不同记录类型的可区分(标记)联合。

¥This form defines discriminated (tagged) union of different record types.

它有必需的成员 discriminatormapping 以及可选的成员 nullablemetadata,不允许有其他成员。

¥It has required members discriminator and mapping and optional members nullable and metadata, no other members are allowed.

discriminator 结构成员的字符串值包含作为联合标记的数据成员的名称。mapping 结构成员包含根据数据中标记成员的值应用的结构字典。mapping 内的结构必须具有 "properties" 形式。

¥The string value of discriminator schema member contains the name of the data member that is the tag of the union. mapping schema member contains the dictionary of schemas that are applied according to the value of the tag member in the data. Schemas inside mapping must have "properties" form.

mapping 内的属性形式不能是 nullable,并且不能定义与鉴别标记相同的属性。

¥Properties forms inside mapping cannot be nullable and cannot define the same property as discriminator tag.

示例 1。

¥Example 1.

结构:

¥Schema:

{
  discriminator: "version",
  mapping: {
    "1": {
      properties: {
        foo: {type: "string"}
      }
    },
    "2": {
      properties: {
        foo: {type: "uint8"}
      }
    }
  }
}

有效数据:{version: "1", foo: "1"}{version: "2", foo: 1}

¥Valid data: {version: "1", foo: "1"}, {version: "2", foo: 1}

无效数据:{}{foo: "1"}{version: 1, foo: "1"}、除对象之外的任何类型

¥Invalid data: {}, {foo: "1"}, {version: 1, foo: "1"}, any type other than object

Example 3:无效模式(映射中定义的鉴别器标记成员)

¥Example 3: invalid schema (discriminator tag member defined in mapping)

{
  discriminator: "version",
  mapping: {
    "1": {
      properties: {
        version: {enum: ["1"]},
        foo: {type: "string"}
      }
    },
    "2": {
      properties: {
        version: {enum: ["2"]},
        foo: {type: "uint8"}
      }
    }
  }
}

# 值形式

¥Values form

dictionary

这种形式定义了一个同质字典,其中成员的值满足给定的结构。

¥This form defines a homogenous dictionary where the values of members satisfy a given schema.

它有一个必需的成员 values(成员值应满足的结构)和可选的成员 nullablemetadata,不允许有其他成员。

¥It has a required member values (schema that member values should satisfy) and optional members nullable and metadata, no other members are allowed.

与 JSON Schema 不同,数据实例必须是 JSON 对象(不使用额外的 type 关键字),并且无法强制执行大小限制。

¥Unlike JSON Schema, the data instance must be JSON object (without using additional type keyword), and there is no way to enforce size restrictions.

示例

¥Example

结构:

¥Schema:

{
  values: {
    type: "uint8"
  }
}

有效数据:{}{"foo": 1}{"foo": 1, "bar": 2}

¥Valid data: {}, {"foo": 1}, {"foo": 1, "bar": 2}

无效数据:{"foo": "bar"},除对象之外的任何类型

¥Invalid data: {"foo": "bar"}, any type other than object

# 参考形式

¥Ref form

reference definitions

此形式定义对根结构的 definitions 成员中相应键中存在的结构的引用。

¥This form defines a reference to the schema that is present in the corresponding key in the definitions member of the root schema.

它有一个必需的成员 ref(根结构中 definitions 对象的成员)和可选成员 nullablemetadata,不允许有其他成员。

¥It has a required member ref (member of definitions object in the root schema) and optional members nullable and metadata, no other members are allowed.

与 JSON Schema 不同,JTD 不允许引用:

¥Unlike JSON Schema, JTD does not allow to reference:

  • 除根级别 definitions 成员之外的任何结构片段

    ¥any schema fragment other than root level definitions member

  • root of the schema - 还有另一种定义自递归模式的方法(参见示例 2)

    ¥root of the schema - there is another way to define a self-recursive schema (see Example 2)

  • 另一个结构文件(但你仍然可以使用 JavaScript 组合多个文件中的结构)。

    ¥another schema file (but you can still combine schemas from multiple files using JavaScript).

示例 1。

¥Example 1.

{
  properties: {
    propFoo: {ref: "foo", nullable: true}
  },
  definitions: {
    foo: {type: "string"}
  }
}

Example 2:二叉树的自引用模式

¥Example 2: self-referencing schema for binary tree

{
  ref: "tree",
  definitions: {
    tree: {
      properties: {
        value: {type: "int32"}
      },
      optionalProperties: {
        left: {ref: "tree"},
        right: {ref: "tree"}
      }
    }
  }
}

Example 3:invalid schema (missing reference)

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

# 空形式

¥Empty form

any data

空 JTD 结构定义可以是任何类型的数据实例,包括 JSON null(即使 nullable 成员不存在)。它不能有除 nullablemetadata 之外的任何成员。

¥Empty JTD schema defines the data instance that can be of any type, including JSON null (even if nullable member is not present). It cannot have any member other than nullable and metadata.

# JTDSchemaType

类型 JTDSchemaType 可用于校验写入的结构是否与你期望校验的类型匹配。这种类型是严格的,如果 TypeScript 编译,你应该不需要进一步的类型保护。这样做的缺点是,JTDSchemaType 可以校验的类型仅限于 JTD 可以校验的类型。如果类型未校验,JTDSchemaType 应解析为 never,当你尝试分配给它时会抛出错误。这意味着像 1 | 2 | 3 这样的类型或一般的无标记联合(字符串字面量联合之外)不能与 JTDSchemaType 一起使用。

¥The type JTDSchemaType can be used to validate that the written schema matches the type you expect to validate. This type is strict such that if typescript compiles, you should require no further type guards. The downside of this is that the types that JTDSchemaType can verify are limited to the types that JTD can verify. If a type doesn't verify, JTDSchemaType should resolve to never, throwing an error when you try to assign to it. This means that types like 1 | 2 | 3, or general untagged unions (outside of unions of string literals) cannot be used with JTDSchemaType.

# 大多数结构

¥Most Schemas

最简单的类型应该与 JTDSchemaType 一起使用,例如

¥Most straightforward types should work with JTDSchemaType, e.g.

interface MyType {
  num: number
  optionalStr?: string
  nullableEnum: "v1.0" | "v1.2" | null
  values: Record<string, number>
}

const schema: JTDSchemaType<MyType> = {
  properties: {
    num: {type: "float64"},
    nullableEnum: {enum: ["v1.0", "v1.2"], nullable: true},
    values: {values: {type: "int32"}},
  },
  optionalProperties: {
    optionalStr: {type: "string"},
  },
}

将编译。将 schema 与 AJV 一起使用将保证类型安全。

¥will compile. Using schema with AJV will guarantee type safety.

# 参考结构

¥Ref Schemas

引用结构稍微高级一些,因为每个定义的类型都必须提前指定。一个简单的引用结构相对简单:

¥Ref schemas are a little more advanced, because the types of every definition must be specified in advance. A simple ref schema is relatively straightforward:

const schema: JTDSchemaType<{val: number}, {num: number}> = {
  definitions: {
    num: {type: "float64"},
  },
  properties: {
    val: {ref: "num"},
  },
}

请注意,所有定义的类型都作为 JTDSchemaType 的第二个参数包含在内。

¥note that the type of all definitions was included as a second argument to JTDSchemaType.

这也适用于递归结构:

¥This also works for recursive schemas:

type LinkedList = {val: number; next?: LinkedList}
const schema: JTDSchemaType<LinkedList, {node: LinkedList}> = {
  definitions: {
    node: {
      properties: {
        val: {type: "float64"},
      },
      optionalProperties: {
        next: {ref: "node"},
      },
    },
  },
  ref: "node",
}

# 值得注意的遗漏

¥Notable Omissions

JTDSchemaType 目前校验,如果结构编译,它将校验准确的类型,但有一些地方可能会出现意外行为。JTDSchemaType 不校验结构是否正确。它不会拒绝根在任何地方定义的结构,也不会拒绝仍在映射属性中定义鉴别器的鉴别器结构。它也不会校验枚举结构是否包含每个枚举成员,因为这在 TypeScript 中通常还不可行。

¥JTDSchemaType currently validates that if the schema compiles it will verify an accurate type, but there are a few places with potentially unexpected behavior. JTDSchemaType doesn't verify the schema is correct. It won't reject schemas that definitions anywhere by the root, and it won't reject discriminator schemas that still define the descriminator in mapping properties. It also won't verify that enum schemas have every enum member as this isn't generally feasible in typescript yet.

# 扩展 JTD

¥Extending JTD

# 元数据结构成员

¥Metadata schema member

每个结构形式可以有一个可选成员 metadata,JTD 为实现/应用特定扩展保留该成员。Ajv 使用此成员作为可以使用任何非标准关键字的位置,例如:

¥Each schema form may have an optional member metadata that JTD reserves for implementation/application specific extensions. Ajv uses this member as a location where any non-standard keywords can be used, such as:

  • Ajv 中包含 union 关键字

    ¥union keyword included in Ajv

  • 任何用户定义的关键字,例如 ajv-keywords (opens new window) 包中定义的关键字

    ¥any user-defined keywords, for example keywords defined in ajv-keywords (opens new window) package

  • JSON Schema 关键字,只要其名称与标准 JTD 关键字不同。如果需要,它可用于实现从 JSON 结构到 JTD 的逐步迁移。

    ¥JSON Schema keywords, as long as their names are different from standard JTD keywords. It can be used to enable a gradual migration from JSON Schema to JTD, should it be required.

扩展是不可移植的

JTD 的特定于 Ajv 的扩展可能不受其他工具的支持,因此虽然它可以简化采用,但它破坏了使用 JTD 的跨平台目标。虽然可以在 metadata 成员中放置一些人类可读的信息,但建议不要在那里添加任何校验逻辑(即使 Ajv 支持)。

¥Ajv-specific extension to JTD are likely to be unsupported by other tools, so while it may simplify adoption, it undermines the cross-platform objective of using JTD. While it is ok to put some human readable information in metadata member, it is recommended not to add any validation logic there (even if it is supported by Ajv).

Ajv 对 metadata 结构成员实现的其他限制:

¥Additional restrictions that Ajv enforces on metadata schema member:

  • 你不能在那里使用标准 JTD 关键字。虽然严格来说这是规范允许的,但这些关键字在 metadata 中应该被忽略 - Ajv 的一般方法是避免任何被忽略的事情。

    ¥you cannot use standard JTD keywords there. While strictly speaking it is allowed by the specification, these keywords should be ignored inside metadata - the general approach of Ajv is to avoid anything that is ignored.

  • 你需要将 metadata 中使用的所有成员定义为关键字。如果它们是空操作,则可以使用 ajv.addKeyword("my-metadata-keyword") 来完成。可以通过禁用 严格模式 (opens new window) 来消除此限制,而不影响 JTD 的严格性 - 未知的关键字在模式本身中仍然被禁止。

    ¥you need to define all members used in metadata as keywords. If they are no-op it can be done with ajv.addKeyword("my-metadata-keyword"). This restriction can be removed by disabling strict mode (opens new window), without affecting the strictness of JTD - unknown keywords would still be prohibited in the schema itself.

# 联合关键字

¥Union keyword

Ajv 定义了在校验 JTD 结构 (meta-schema (opens new window)) 的结构中使用的 union 关键字。

¥Ajv defines union keyword that is used in the schema that validates JTD schemas (meta-schema (opens new window)).

该关键字只能在 metadata 结构成员内部使用。

¥This keyword can be used only inside metadata schema member.

union 关键字是不可移植的

此关键字是非标准的,其他 JTD 工具不支持它,因此如果你希望数据跨平台,建议不要在数据结构中使用此关键字。

¥This keyword is non-standard and it is not supported in other JTD tools, so it is recommended NOT to use this keyword in schemas for your data if you want them to be cross-platform.

# 用户定义的关键字

¥User-defined keywords

任何可在 JSON Schema 结构中使用的用户定义关键字也可以在 JTD 结构中使用,包括 ajv-keywords (opens new window) 包中的关键字。

¥Any user-defined keywords that can be used in JSON Schema schemas can also be used in JTD schemas, including the keywords in ajv-keywords (opens new window) package.

用户定义的关键字不可移植

强烈建议仅使用它来简化从 JSON Schema 到 JTD 的迁移,而不要在新结构中使用非标准关键字,因为任何其他工具都不支持这些关键字。

¥It is strongly recommended to only use it to simplify migration from JSON Schema to JTD and not to use non-standard keywords in the new schemas, as these keywords are not supported by any other tools.

解析不支持非标准 JTD 关键字

compileParser 方法不支持非标准 JTD 关键字,你必须使用 JSON.parse 然后进行校验。

¥compileParser method does not support non-standard JTD keywords, you will have to use JSON.parse and then validates.

# 校验错误

¥Validation errors

TODO