| | |

JSON 06 ๐Ÿ“„ JSON Schema Fundamentals and Type Declarations

โฑ๏ธ Estimated read: 10 min | |

JSON Schema is a declarative language for describing the structure, the constraints, and the semantics of a JSON document. It is itself written in JSON, and it is used to validate the instances, to generate the documentation, and to produce the TypeScript types. The specification is split into two parts: the Core, which defines the basic foundation and the vocabulary system, and the Validation, which defines the keywords that constrain the instance values . The current version is 2020-12, and the schema is identified by the $schema keyword that points to the meta-schema URL .

The schema is not a type system in the programming-language sense. It is a set of assertions that the instance must satisfy, and the assertions are the keywords. The type keyword asserts that the instance is one of the six core types: object, array, number, string, boolean, or null . The properties keyword asserts that the object’s properties match the given subschemas, and the required keyword asserts that the named properties are present . The items keyword asserts that the array’s elements match the given subschema . The keywords are composable, and the allOf, anyOf, oneOf, and not are the mechanisms that combine the subschemas.

Key point: JSON Schema 2020-12 is the current version, split into the Core and the Validation vocabularies. The type keyword asserts the core type, the properties and the required constrain the objects, and the items constrains the arrays. The json-schema-to-typescript package generates the TypeScript declarations from the schema, and the generated types are the bridge between the runtime validation and the compile-time type safety.


Why JSON Schema exists

The validation problem. A JSON document is just text, and the structure is implicit. A schema makes the structure explicit, and the validator checks that the instance satisfies the assertions. The validation is the mechanism that keeps the malformed data out of the application, and it is the same mechanism that the API gateways and the form libraries use.

The documentation problem. The schema is a machine-readable description of the data, and the documentation tools can render it as the human-readable reference. The title and the description keywords are the annotations, and the annotations are the mechanism that attaches the documentation to the schema . The schema is the single source of truth for the structure and the documentation.

The type-generation problem. The schema is the runtime description, and the TypeScript types are the compile-time description. The json-schema-to-typescript package compiles the schema into the TypeScript declarations, and the generated types are the mechanism that keeps the runtime and the compile-time in sync . The schema is the source, and the types are the derived artifact.

The trade-off. The schema is the extra artifact to maintain, and the schema can drift from the code that uses it. The drift is the cost of the single source of truth, and the code generation is the mechanism that keeps the drift visible. The schema is the contract, and the generated types are the enforcement.


a. The core keywords and the type declarations

The type keyword is the assertion that the instance is one of the six core types. The value can be a string or an array of strings, and the array is the union of the types.

// The type keyword
{
  "type": "string"
}

// The union type
{
  "type": ["string", "null"]
}

The properties keyword is the assertion that the object’s properties match the subschemas. The required keyword is the assertion that the named properties are present. The additionalProperties keyword is the assertion that the unspecified properties are allowed or rejected .

// The object schema
{
  "type": "object",
  "properties": {
    "name": { "type": "string" },
    "age": { "type": "integer", "minimum": 0 }
  },
  "required": ["name"],
  "additionalProperties": false
}

The items keyword is the assertion that the array’s elements match the subschema. The minItems and the maxItems are the length constraints, and the uniqueItems is the uniqueness constraint .

// The array schema
{
  "type": "array",
  "items": { "type": "string" },
  "minItems": 1,
  "maxItems": 10
}

The enum keyword is the assertion that the instance is one of the listed values, and the const is the assertion that the instance is the single value. The pattern is the assertion that the string matches the regular expression, and the format is the semantic assertion for the date, the email, and the other well-known formats .


b. The type generation with json-schema-to-typescript

The json-schema-to-typescript package compiles the schema into the TypeScript declarations. The CLI is json2ts, and the API is the compile and compileFromFile functions .

# The CLI
json2ts -i schema.json -o types.d.ts

# The API
npm install json-schema-to-typescript

The compile function accepts the schema object and the type name, and it returns the TypeScript source. The compileFromFile reads the schema from the file, and the options control the output style and the strictness .

// The compile function
import { compile } from "json-schema-to-typescript";

const schema = {
  type: "object",
  properties: {
    name: { type: "string" },
    age: { type: "integer" },
  },
  required: ["name"],
};

const types = await compile(schema, "User");
// export interface User {
//   name: string;
//   age?: number;
// }

The additionalProperties: false option is the mechanism that tells the generator to omit the index signature. The unknownAny option is the mechanism that uses unknown instead of any, and the declareExternallyReferenced is the mechanism that declares the external schemas referenced by the $ref .

// The options
const types = await compile(schema, "User", {
  additionalProperties: false,
  unknownAny: true,
  declareExternallyReferenced: true,
});

The generated types are the compile-time bridge, and the schema is the runtime source. The two are the same contract, and the generation is the mechanism that keeps them aligned.


Complete Example Session

// ============================================
// PART 1: THE BASIC SCHEMA
// ============================================

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "name": { "type": "string" },
    "age": { "type": "integer", "minimum": 0 }
  },
  "required": ["name"]
}

// The schema describes the object.
// The name is required.
// The age is optional and non-negative.

// ============================================
// PART 2: THE TYPE KEYWORD
// ============================================

{ "type": "string" }
{ "type": ["string", "null"] }

// The type is the core assertion.
// The array is the union.

// ============================================
// PART 3: THE OBJECT KEYWORDS
// ============================================

{
  "type": "object",
  "properties": { "email": { "type": "string", "format": "email" } },
  "required": ["email"],
  "additionalProperties": false
}

// The properties is the subschema map.
// The required is the presence assertion.
// The additionalProperties is the strictness.

// ============================================
// PART 4: THE ARRAY KEYWORDS
// ============================================

{
  "type": "array",
  "items": { "type": "number" },
  "minItems": 1
}

// The items is the element schema.
// The minItems is the length assertion.

// ============================================
// PART 5: THE ENUM AND CONST
// ============================================

{ "enum": ["active", "inactive"] }
{ "const": "fixed" }

// The enum is the closed set.
// The const is the single value.

// ============================================
// PART 6: THE COMPOSITION
// ============================================

{
  "allOf": [
    { "type": "object", "properties": { "a": { "type": "string" } } },
    { "type": "object", "properties": { "b": { "type": "number" } } }
  ]
}

// The allOf is the conjunction.
// The anyOf is the disjunction.
// The oneOf is the exclusive disjunction.
// The not is the negation.

// ============================================
// PART 7: THE REFERENCE
// ============================================

{
  "type": "object",
  "properties": {
    "address": { "$ref": "#/$defs/address" }
  },
  "$defs": {
    "address": { "type": "object", "properties": { "city": { "type": "string" } } }
  }
}

// The $ref is the reuse mechanism.
// The $defs is the definitions container.

// ============================================
// PART 8: THE TYPE GENERATION
// ============================================

// json2ts -i schema.json -o types.d.ts

// The generated TypeScript:
// export interface User {
//   name: string;
//   age?: number;
// }

// ============================================
// PART 9: THE GENERATED TYPE OPTIONS
// ============================================

// The additionalProperties: false option
// omits the index signature.
// The unknownAny: true option uses unknown.

// ============================================
// PART 10: THE CHECKLIST
// ============================================

// [ ] The $schema points to the 2020-12 meta-schema.
// [ ] The type keyword is set.
// [ ] The properties and the required are set for the objects.
// [ ] The items is set for the arrays.
// [ ] The $defs and the $ref are used for the reuse.
// [ ] The json-schema-to-typescript is installed for the types.
// [ ] The generated types are committed or generated in the build.

The ten parts covered the basic schema, the type keyword, the object keywords, the array keywords, the enum and const, the composition, the reference, the type generation, the generated type options, and the checklist. The through-line is that the schema is the runtime contract, and the generated types are the compile-time bridge.


Quick Reference

The core types

TypeDescription
objectThe unordered string-keyed map
arrayThe ordered list
numberThe arbitrary-precision decimal
stringThe Unicode sequence
booleanThe true or false
nullThe null value

The validation keywords

KeywordApplies toPurpose
typeAnyThe core type assertion
propertiesObjectThe property subschemas
requiredObjectThe required property names
additionalPropertiesObjectThe unspecified property policy
itemsArrayThe element subschema
minItems / maxItemsArrayThe length constraints
enumAnyThe closed value set
constAnyThe single value
patternStringThe regex constraint
formatStringThe semantic format

The composition keywords

KeywordMeaning
allOfAll the subschemas must validate
anyOfAt least one subschema must validate
oneOfExactly one subschema must validate
notThe subschema must not validate

The type generation

ToolCommandPurpose
json-schema-to-typescriptjson2ts -i schema.json -o types.d.tsThe CLI generation
compilecompile(schema, "User")The API generation
compileFromFilecompileFromFile("schema.json")The file-based generation

Best Practices

โœ… Do This:

// Use the $schema keyword
{ "$schema": "https://json-schema.org/draft/2020-12/schema" }       // โœ…

// Use the required for the mandatory fields
{ "required": ["name"] }                                           // โœ…

// Use the additionalProperties: false for the strict objects
{ "additionalProperties": false }                                  // โœ…

โŒ Don’t Do This:

// Omit the type keyword
{ "properties": { "name": {} } } // the type is unconstrained        // โŒ

// Use the required without the properties
{ "required": ["name"] } // the name is not described                // โŒ

Common Pitfalls

PitfallWhy It HappensFix
The generated type has an index signatureThe additionalProperties is not falseSet additionalProperties: false
The type is anyThe unknownAny is not setSet unknownAny: true
The $ref is not resolvedThe declareExternallyReferenced is not setSet the option or inline the schema
The schema does not validateThe $schema is missingAdd the $schema keyword

Real-World Examples

1. The string type

{ "type": "string" }

2. The object type

{ "type": "object", "properties": { "name": { "type": "string" } } }

3. The required

{ "required": ["name"] }

4. The array

{ "type": "array", "items": { "type": "number" } }

5. The enum

{ "enum": ["active", "inactive"] }

6. The $ref

{ "$ref": "#/$defs/address" }

7. The allOf

{ "allOf": [{ "type": "object" }, { "required": ["a"] }] }

8. The type generation

json2ts -i schema.json -o types.d.ts

9. The compile API

const types = await compile(schema, "User");

10. The generated type

export interface User { name: string; age?: number; }

Visual

The Schema and the Instance

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  THE SCHEMA VALIDATES THE INSTANCE                           โ”‚
โ”‚                                                              โ”‚
โ”‚  The schema                                                  โ”‚
โ”‚  {                                                           โ”‚
โ”‚    "type": "object",                                         โ”‚
โ”‚    "properties": { "name": { "type": "string" } },           โ”‚
โ”‚    "required": ["name"]                                      โ”‚
โ”‚  }                                                           โ”‚
โ”‚         โ”‚                                                    โ”‚
โ”‚         โ–ผ                                                    โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                    โ”‚
โ”‚  โ”‚ The validator runs  โ”‚  The instance is checked           โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                    โ”‚
โ”‚             โ”‚                                                โ”‚
โ”‚      โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”                                         โ”‚
โ”‚      โ–ผ             โ–ผ                                         โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                    โ”‚
โ”‚  โ”‚ Valid  โ”‚   โ”‚Invalid โ”‚                                    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                    โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The Type Generation

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  THE SCHEMA IS THE SOURCE, THE TYPES ARE THE DERIVED          โ”‚
โ”‚                                                              โ”‚
โ”‚  schema.json                                                 โ”‚
โ”‚         โ”‚                                                    โ”‚
โ”‚         โ–ผ                                                    โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                    โ”‚
โ”‚  โ”‚ json-schema-to-     โ”‚  The compile step                  โ”‚
โ”‚  โ”‚ typescript          โ”‚                                    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                    โ”‚
โ”‚             โ”‚                                                โ”‚
โ”‚             โ–ผ                                                    โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                    โ”‚
โ”‚  โ”‚ types.d.ts          โ”‚  The TypeScript declarations       โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                    โ”‚
โ”‚             โ”‚                                                โ”‚
โ”‚             โ–ผ                                                    โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                    โ”‚
โ”‚  โ”‚ The application     โ”‚  The compile-time type safety      โ”‚
โ”‚  โ”‚ imports the types   โ”‚                                    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                    โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The Composition

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  THE COMPOSITION KEYWORDS                                    โ”‚
โ”‚                                                              โ”‚
โ”‚  allOf: all the subschemas must validate                     โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                    โ”‚
โ”‚  โ”‚ A โˆง B โˆง C           โ”‚                                    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                    โ”‚
โ”‚                                                              โ”‚
โ”‚  anyOf: at least one must validate                           โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                    โ”‚
โ”‚  โ”‚ A โˆจ B โˆจ C           โ”‚                                    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                    โ”‚
โ”‚                                                              โ”‚
โ”‚  oneOf: exactly one must validate                            โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                                    โ”‚
โ”‚  โ”‚ A โŠ• B โŠ• C           โ”‚                                    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                                    โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

ItemValue
Current version2020-12
The core typesobject, array, number, string, boolean, null
The object keywordsproperties, required, additionalProperties
The array keyworditems
The compositionallOf, anyOf, oneOf, not
The type generationjson-schema-to-typescript
The CLIjson2ts

Key takeaways:

  • JSON Schema 2020-12 is the current version, split into the Core and the Validation vocabularies. The Core defines the foundation and the vocabulary system, and the Validation defines the keywords that constrain the instance values . The $schema keyword points to the meta-schema URL .
  • The type keyword asserts the core type. The six types are object, array, number, string, boolean, and null. The value can be a string or an array of strings for the union .
  • The properties and the required constrain the objects, and the items constrains the arrays. The additionalProperties is the strictness policy for the unspecified properties .
  • The json-schema-to-typescript package generates the TypeScript declarations. The compile and the compileFromFile are the API, and the json2ts is the CLI . The generated types are the compile-time bridge.
  • The schema is the runtime contract, and the types are the compile-time enforcement. The generation is the mechanism that keeps them aligned, and the additionalProperties: false is the option that produces the strict types.

Remember: JSON Schema is the declarative language for the JSON structure. The schema is the runtime contract, and the generated TypeScript types are the compile-time bridge. The type keyword is the core assertion, the properties and the required are the object constraints, and the items is the array constraint. The json-schema-to-typescript package generates the types, and the generation is the mechanism that keeps the runtime and the compile-time in sync.

Was this helpful?


Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!