JSON X-Type is a data type notation for describing JSON-like structures in a simple and natural way. Any valid JSON can be validated against a JSON X-Type definition.
JSON X-Type can be described by itself (🔗).
| Value | Description |
|---|---|
| string | String type. |
| number | Number type. |
| boolean | Boolean type. |
null |
The null value. (Note: The string "null" has no special meaning.) |
| undefined | Indicates that the value is not set. |
| any | Any value (not validated). |
Primitive types are the basic building blocks of JSON X-Type and can be used in JSON values.
Any other values are considered literals.
To use a reserved keyword as a literal (rather than its special meaning), it must be escaped with the $literal: prefix (🔗).
Special keywords are used to compose complex types and can be used in JSON keys.
| Key | Description |
|---|---|
| $record | Record generic (🔗). |
| $array | Array generic (🔗). |
| $and | Types intersection (🔗). |
| $ref | Reference to another JSON X-Type (🔗). |
The list can be extended with other $-prefixed keywords.
To use a literal key that starts with $, it must be escaped with the $literal: prefix (🔗).
Note that $-prefixed keys mostly cannot be combined with each other at the same level:
{
"$array": "string",
"$record": "boolean"
}This should result in the undefined type.
Object literals define object-like structures:
{
"name": "string",
"age": "number"
}The example above defines an object with two required properties.
Record types allow you to define objects with dynamic properties using the $record keyword:
{
"$record": "boolean"
}This defines an object where any property has a boolean value.
A TypeScript analogy for $record is Record<string, T>.
You can also combine specific properties with dynamic ones, although it's not recommended:
{
"name": "string",
"$record": "any"
}This defines an object with a required name property of type string, plus any number of additional properties of any type.
The $record key puts constraints on all properties, including defined ones.
Array types define collections using the $array keyword:
{
"$array": "string"
}This defines an array of strings.
A TypeScript analogy for $array is Array<T> or T[].
Array literals define multiple options, one of which is applicable:
["string", "undefined"]This defines a value that can be either a string or undefined. Note that this is different from array types (see Array types).
The equivalent TypeScript notation uses the pipe operator (|) to express unions.
It is possible to combine several types (mainly existing object types) into one using the $and keyword:
{
"$and": [
{
"foo": "string"
},
{
"bar": "number"
}
]
}The result is an object that includes all of the properties of all the items:
{
"foo": "string",
"bar": "number"
}A TypeScript analogy for $and is the following:
type And<U> = (U extends any ? (k: U) => void : never) extends (
k: infer I
) => void
? I
: never
type Combined = And<{foo: string} | {bar: number}> // {"foo": "string"} & {"bar": "number"} ≡ {"foo": "string", "bar": "number"}Effectively, it applies the AND relation between the array members, replacing the OR relation.
Note that it doesn't make sense to combine primitive types or objects that have common properties with different types:
{
"$and": [
{
"foo": "string"
},
{
"foo": "number"
}
]
}The above example results in foo being both string and number, which is effectively equivalent to TypeScript's never type.
Impossible combinations should result in the undefined type.
Whenever there is a need to use a literal string value instead of a reserved keyword, it must be prepended with the $literal: prefix:
{
"$literal:$record": "boolean"
}This checks for an object with the $record key of a boolean value, e.g.:
{
"$record": true
}Similarly, you can escape type values like "string":
{
"foo": "$literal:string"
}It is possible to refer to other JSON X-Types using the JSON Pointer syntax:
{
"foo": {
"$ref": "#/bar"
},
"bar": ["string", "number", "boolean"]
}A reference must be resolved relative to the file it appears in.
The $ref expression must be replaced with the value of the field it refers to.
Any other properties alongside the $ref must be ignored.
Alternatively, the $ref keyword can be used as a prefix, which is easier to write and read (although the support depends on the tooling):
{
"foo": "$ref:./path/to/file"
}If a reference cannot be resolved, it should be treated as any.
Possible extensions are described here.