Type-level conversion of JSON Schema (draft 7) into TypeScript types: handle JSON Schemas as TypeScript types without code generation.
import { Schema } from `json-schema-type-mapper` declare const myString: Schema<{ type: 'string' }> // resolves to --> stringyarn add json-schema-type-mapper
Suppose you're dealing with the following JSON Schema:
interface User { type: 'object' properties: { id: { type: 'number' } name: { type: 'string' } } required: ['id'] additionalProperties: false }And a function whose input conforms to that schema:
function saveUser(user: any) { // ... }The question is, how would you define user parameter's type? There's a couple of ways to go about it. Either
- a) manually write out a TypeScript type definition that matches the above JSON Schema, or
- b) use a code generation tool such as
json-schema-to-typescriptto convert the JSON Schema to a TypeScript interface.
I'd probably go the manual route in a simple case like this and opt for code generation with more complex schemas. This library, however, provides a bit of a middle ground between the two by leveraging the type system:
import { Schema } from 'json-schema-type-mapper' function saveUser(user: Schema<User>) { // ... }Now user's type resolves to { id: number; name?: string }. We get automatic conversion from JSON Schema to TypeScript, all by leveraging the type system.
Compared to code generation, this method has a number of limitations resulting from the type system's limitations. We get pretty far though, which in itself is testament to the impressive capabilities of TypeScript's type system.
For a thorough list of supported features and examples check the test file.
type User = Schema<{ definitions: { address: { $id: '#address' properties: { city: { type: 'string' } country: { type: 'string'; enum: ['FI', 'SV', 'NO'] } } required: ['city', 'country'] } } allOf: [ { $ref: '#address' }, { type: 'object' properties: { id: { anyOf: [ { type: 'number' }, { type: 'array'; items: { type: 'number' } } ] } name: { type: ['string', 'null'] } } required: ['name'] } ] }> // resolves to: type User = { [x: string]: JSONValue name: string | null id?: number | number[] | undefined city: string country: "FI" | "SV" | "NO" }- define schema as variable (
const schema = { ... } as const) instead of interface- requires handling something like
DeepReadonly<JSONSchema>sinceas constturns everythingreadonly
- requires handling something like
- object
dependencies -
allOf,anyOf -
if,then,else -
$id
- path references like
{ "$ref": "#/definitions/User" }; consider using$ids instead! notonly works on primitive types such as{ "not": { "type": ["number", "string"] } }- object
propertyNames - object
minProperties/maxProperties - object
patternProperties - tuple
items: [...]limited to a maximum of 6 items for now - array
contains - array
minItems/maxItems- hardcoding might work when list length is below a reasonable number...?
- array
uniqueness- maybe feasible for primitives, not so much for objects?
oneOf
- as-typed with a similar effort of type-level Schema-to-Typescript conversion
- json-schema-typed provides TypeScript definitions for JSON Schema objects