Align TypeScript Standards With JSON Schema
Published: Sep 20, 2020
Last updated: Sep 20, 2020
In my current work, I am looking at ways to try enforce particular standards across multiple and larger projects.
This means standards set across different languages that are flexible, extensible and kept up to date.
A few of my upcoming posts will looking into some of my spikes that I am doing to as investigation, starting with the json-schema-to-typescript
library.
JSON Schema
What is JSON Schema? Here is a definition from the JSON Schema Org site:
JSON Schema is a powerful tool for validating the structure of JSON data.
The hope is that I can use tooling for JSON schema and Open API to help with structuring micro-services and providing "cheap" contract testing.
Setting up the project
mkdir json-schema-ts-spike cd json-schema-ts-spike # start a yarn project with default settings yarn init -y yarn add json-schema-to-typescript jsonschema # setup files we will use touch index.js book.json
Compiling From Source
In my example, I will opt to generate by reading in from a particular file.
const Validator = require("jsonschema").Validator; const { compile, compileFromFile } = require("json-schema-to-typescript"); const fs = require("fs"); const path = require("path"); const main = async () => { // validate the schema first const v = new Validator(); // read the schema details const schemaFilepath = path.join(__dirname, "book.json"); const bookSchema = JSON.parse(fs.readFileSync(schemaFilepath, "utf-8")); // read the example const exampleJsonFilepath = path.join(__dirname, "example.json"); const exampleJson = JSON.parse(fs.readFileSync(exampleJsonFilepath, "utf-8")); v.addSchema(bookSchema, "/BookSchema"); const validation = v.validate(exampleJson, bookSchema); if (validation.errors.length) { console.log(validation.errors); process.exit(1); } // compile from file const ts = await compileFromFile(schemaFilepath); fs.writeFileSync("book.d.ts", ts); }; main();
This will be all the code we need for our example.
The JSON Schema file
For this part, let's model a basic book and a collection. We need to add some schema info the the book.json
file.
I won't go too deep into the modelling itself with JSON schema, but these are the definitions I am coming up with:
{ "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { "user": { "type": "object", "properties": { "name": { "type": "string" }, "preferredName": { "type": "string" }, "age": { "type": "string" }, "gender": { "enum": ["male", "female", "other"] } }, "required": ["name", "preferredName", "age", "gender"] }, "author": { "type": "object", "properties": { "allOf": [{ "$ref": "#/definitions/address" }] } } }, "type": "object", "properties": { "author": { "$ref": "#/definitions/author" }, "title": { "type": "string" }, "publisher": { "type": "string" } }, "required": ["author", "title", "publisher"] }
The Book JSON
Let's add some info to our basic example.json
file that we can test against:
{ "author": { "name": "Dennis O'Keeffe", "preferredName": "Dennis", "age": 28, "gender": "male" }, "title": "The Greatness Of Strict Schemas", "publisher": "Real Publisher (definitely not fake)" }
Running Our Creation
Run node index.js
from the root directory.
You will actually notice that I left a mistake in there! The following will log out:
> node index.js [ ValidationError { property: 'instance.author.age', message: 'is not of a type(s) string', schema: { type: 'string' }, instance: 28, name: 'type', argument: [ 'string' ], stack: 'instance.author.age is not of a type(s) string' } ]
Our validation (or invalidation per se) was a success! We said in the schema that it should be a string but we received the number 28
.
Head back to book.json
and convert the value to type number
. Now if we run it again node index.js
again, we will get some success! We will even see our books.d.ts
file has been written.
You might be wondering why I am writing this blog post in
js
to generate TypeScript types. Honestly, when I am doing spikes I normally just write quick scripts and keep out the fluff.
You will see the following is generated:
/* tslint:disable */ /** * This file was automatically generated by json-schema-to-typescript. * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, * and run json-schema-to-typescript to regenerate this file. */ export interface BookSchema { author: User; title: string; publisher: string; [k: string]: unknown; } export interface User { name: string; preferredName: string; age: number; gender: "male" | "female" | "other"; [k: string]: unknown; }
Great success! We now have a type for our schema that we can import in.
Resources and Further Reading
Image credit: Tony Pham
Align TypeScript Standards With JSON Schema
Introduction