Stop typing twice 🙅♂️
A lot of projects use JSON schemas for runtime data validation along with TypeScript for static type checking.
Their code will probably look like this:
const dogSchema = {
type: "object",
properties: {
name: { type: "string" },
age: { type: "integer" },
hobbies: { type: "array", items: { type: "string" } },
favoriteFood: { enum: ["pizza", "taco", "fries"] },
},
required: ["name", "age"],
};
type Dog = {
name: string;
age: number;
hobbies?: string[];
favoriteFood?: "pizza" | "taco" | "fries";
};
Both objects carry similar if not exactly the same information. This is a code duplication that can annoy developers and introduce bugs if not properly maintained.
That's where json-schema-to-ts
comes to the rescue 💪
FromSchema
The FromSchema
method allows infering TS types directly from JSON schemas:
import { FromSchema } from "json-schema-to-ts";
const dogSchema = {
type: "object",
properties: {
name: { type: "string" },
age: { type: "integer" },
hobbies: { type: "array", items: { type: "string" } },
favoriteFood: { enum: ["pizza", "taco", "fries"] },
},
required: ["name", "age"],
} as const;
type Dog = FromSchema<typeof dogSchema>;
Note: The as const
statement is used so that TypeScript takes the schema definition to the word (e.g. true is interpreted as the true constant and not as boolean). It is pure TypeScript and has zero impact on the compiled code.
Docs
Installation
npm install --save-dev json-schema-to-ts
yarn add --dev json-schema-to-ts
Use cases
Litterals
const litteralSchema = {
type: "null",
} as const;
type Litteral = FromSchema<typeof litteralSchema>;
Objects
const objectSchema = {
type: "object",
properties: {
foo: { type: "string" },
bar: { type: "number" },
},
required: ["foo"],
} as const;
type Object = FromSchema<typeof objectSchema>;
Arrays
const arraySchema = {
type: "array",
items: { type: "string" },
} as const;
type Array = FromSchema<typeof arraySchema>;
Tuples
const tupleSchema = {
type: "array",
items: [{ type: "boolean" }, { type: "string" }],
} as const;
type Tuple = FromSchema<typeof tupleSchema>;
FromSchema
supports the additionalItems
keyword.
const tupleSchema = {
type: "array",
items: [{ type: "boolean" }, { type: "string" }],
additionalItems: false,
}
type Tuple = FromSchema<typeof tupleSchema>;
const tupleInstance: Tuple = [true, "string", 42] => ❌
Const
const fooSchema = {
const: "foo",
} as const;
type Foo = FromSchema<typeof fooSchema>; // => "foo"
Enums
const enumSchema = {
enum: [true, 42, { foo: "bar" }],
} as const;
type Enum = FromSchema<typeof enumSchema>; // => true | 42 | { foo: "bar"}
enum
can be used concurrently with type
.
const enumSchema = {
type: "string",
enum: ["foo", "bar", { foo: "bar" }],
} as const;
type Enum = FromSchema<typeof enumSchema>; // => "foo" | "bar"
If used in concurrency with const
, the enum
keyword will be omitted.
Nested schemas
Nested schemas will work fine as long as you don't forget the as const
statement!
const childSchema = {
type: "object",
properties: {
foo: { type: "string" },
bar: { type: "integer" },
},
required: ["foo", "bar"],
} as const;
const parentSchema = {
type: "array",
items: childSchema,
} as const;
type Parent = FromSchema<typeof parentSchema>;