In my current work, I am looking at ways to try to 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 be 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
Originally posted on my blog. Follow me on Twitter for more hidden gems @dennisokeeffe92.
Top comments (0)