/json-typescript-decoder

Type safe JSON validation for Typescript

Primary LanguageTypeScriptMIT LicenseMIT

Typescript JSON decoder

Generates Typescript typings and decoders from a JSON schema that will validate and convert json to the correct Typescript typings. The generated decoders allow you to consume JSON in a type-safe way.

Under the hood this package combines the power of json-schema-to-typescript and Ajv and Ajv-Pack with generated decoders.

The JSON validation methods are pre-generated for super-fast validation at runtime.

Usage

Generate typescript code from JSON schema

const generateFromFile = require('json-typescript-decoder').generateFromFile;

generateFromFile(
  'schema.json', // The input schema
  'sample', // The output folder
  options: { // Optional options
    // Custom decoder name, by default <OutputFolder>Decoder
    decoderName: 'MySampleDecoder',

    // Prettier configuration for generated Typescript
    // See https://prettier.io/docs/en/options.html
    style: { singleQuote: true, trailingComma: 'all' },

    // Ajv options for generated code
    // See https://github.com/epoberezkin/ajv#options
    ajvOptions: { removeAdditional: true },
  },
).catch(console.error);

Include Ajv in your package.json

Ajv should still be a run-time dependency, but generated modules will only depend on some parts of it, the whole Ajv will not be included in the bundle if you require these modules from your code.

npm install ajv

Enable Javascript in your tsconfig.json

Because the generated validator functions from Ajv-Pack are Javascript, you need to enable Javascript in your tsconfig.json.

Add "allowJs": true to the compilerOptions.

Example

Example JSON Schema

{
  "definitions": {
    "address": {
      "type": "object",
      "properties": {
        "street_address": { "type": "string" },
        "city": { "type": "string" },
        "state": { "type": "string" }
      },
      "required": ["street_address", "city", "state"],
      "additionalProperties": false
    },
    "person": {
      "type": "object",
      "properties": {
        "firstName": { "type": "string" },
        "lastName": { "type": "string" },
        "address": { "$ref": "#/definitions/address" }
      },
      "required": ["firstName", "lastName"],
      "additionalProperties": false
    }
  }
}

Decode from JSON

import { SampleDecoder } from './sample';

// 👍 Person type is returned and we know for sure that the JSON is valid
const validPerson = SampleDecoder.Person({ firstName: "John", lastName: "Doe" });

// 💥 Throws an error: should have required property 'lastName'
const invalidPerson = SampleDecoder.Person({ firstName: "John" });

Generated Typescript:

This is what the generated code looks like (more or less).

// Import Javascript validator functions generated by Ajv-Pack
import * as Address$validate from "./Address.validate.js";
import * as Person$validate from "./Person.validate.js";

export interface Address {
  street_address: string;
  city: string;
  state: string;
}

export interface Person {
  firstName: string;
  lastName: string;
  address?: Address;
}

// ~~~ some generated utility functions here ~~~

export class SampleDecoder {
  static Address = decode<Address>(Address$validate, "Address");
  static Person = decode<Person>(Person$validate, "Person");
}

Useful Ajv options

See https://github.com/epoberezkin/ajv#options for a complete list of Ajv options.

removeAdditional

By default, if you specify "additionalProperties": false in your JSON schema, schema validation fails if there are additional properties in the JSON. Sometimes you don't want this behaviour because, for example, a REST api may add properties to the JSON output and you don't want old version of you code to crash.

If you specify removeAdditional: true in the ajvOptions then all additional properties will be removed from the input JSON and schema validation will pass.

Example

Too clarify, an example based on the above schema.

// Without removeAdditional
// Will fail with an error because JSON has a additional property 'age'
console.info(SampleDecoder.Person({ firstName: "John", lastName: "Doe", age: 30 }));

// With removeAdditional = true
// Succeeds and age property is stripped from the input JSON
// This will print { firstName: "John", lastName: "Doe" }
console.info(SampleDecoder.Person({ firstName: "John", lastName: "Doe", age: 30 }));

You can also pass "failing", "all" and false as a value for removeAdditional. Please read the Ajv documentation for more information.