Skip to content

A walkthrough for generating JSON schemas from TypeScript interfaces.

Notifications You must be signed in to change notification settings

ryanburr/validating-json-with-typescript-interfaces

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

16 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Validating JSON with TypeScript Interfaces

A walk-through for generating JSON schemas from TypeScript interfaces.

Here are some slides that correspond to this repository.

Background

I previously gave a talk at the Detroit React meetup about how Signal Advisors uses React to create a library of email templates. One of the questions after the talk was about how we populate data into those templates. This repository demonstrates the technique we use to confidently send thousands of emails to customers with confidence.

Template Syntax

Our templates use handlebar.js, a tool for using {{mustache}} syntax to interpolate data.

const template = Handlebars.compile(`
  <p>Hi, my name is {{name}} and I'm from {{hometown}}.</p>
`);

const data = { name: 'Ryan Burr', hometown: 'Rochester, Michigan' };

const result = template(data);

console.log(result);
// <p>Hi, my name is Ryan Burr and I'm from Rochester, Michigan.</p>

Great, now we have a means of injecting data into our email templates, but what about the developer experience? How can we be sure that the data payload contains the required properties used within out template?

This was a major concern for Signal. We needed to ensure that we were sending high-quality emails to our customers with every notification we create.

Defining our Schema with TypeScript

TypeScript was a perfect fit for describing the shape of the data needed for our notification templates. We can define properties as primitives or compose interfaces to describe more complex data types.

interface TemplatePayload {
  name: string;
  hometown: string;
  employer?: string;
}

const data: TemplatePayload = { 
  name: 'Ryan Burr', 
  hometown: 'Rochester, Michigan',
  employer: 'Signal Advisors'
};
interface Employer {
  name: string,
  location: string
}

interface Person {
  name: string;
  hometown: string;
  employer?: Pick<Employer, 'name'>;
}

interface TemplatePayload {
  person: Person;
}

const template = Handlebars.compile(`
  <p>Hi, my name is {{person.name}} and I'm from {{person.hometown}}.</p>
  {{#if person.employer }}
    <p>I work for {{person.employer.name}}.</p>
  {{/if}}
`);

const data: TemplatePayload = { 
  person: {
    name: 'Ryan Burr', 
    hometown: 'Rochester, Michigan',
  },
  employer: {
    name: 'Signal Advisors',
    location: 'Detroit'
  }
};

const result = template(data);

console.log(result);
// <p>Hi, my name is Ryan Burr and I'm from Rochester, Michigan.</p>
// <p>I work for Signal Advisors who is headquartered in Detroit, Michigan.</p>

Using TypeScript interfaces works great in this context, but what if we were receiving data from a REST endpoint where the caller doesn't have access to the type definitions?

Create Notification Use Case Diagram

We need a way to share the schema we defined so it can be used at runtime.

TypeScript Interface to JSON Schema

JSON Schema is a specification that allows you to annotate and validate JSON data.

{
  "type": "object",
  "properties": {
    "person": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string"
        },
        "hometown": {
          "type": "string"
        },
        "employer": {
          "type": "object",
          "properties": {
            "name": {
              "type": "string"
            },
            "location": {
              "type": "string"
            }
          },
          "required": [
            "name",
            "location"
          ]
        }
      },
      "required": [
        "name",
        "hometown"
      ]
    }
  },
  "required": {
    "person"
  }
}

This JSON schema represents the TemplatePayload mentioned earlier. The more complex your data model gets, the more unwieldy this schema will get. It quickly becomes too cumbersome to manage by hand.

That is where a tool such as typescript-json-schema comes in. With this, we are able to generate this JSON schema right from our interface with required properties, extends, annotation keywords, and property initializers as defaults translated into our schema.

const program = TJS.getProgramFromFiles(['src/schemas/example.schema.ts']);
const schema = TJS.generateSchema(program);

console.log(schema);
/**
 {
  "$schema": "http://json-schema.org/draft-07/schema#",
  "definitions": {
    "Example1Payload": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string"
        },
        "hometown": {
          "type": "string"
        }
      },
      "additionalProperties": false,
      "required": [
        "hometown",
        "name"
      ]
    }
  }
}
 * /

Examples

Example 1 - Basic

  • Add name and hometown props to payload interface
  • Compile and show output
  • Validate test payloads

Example 2 - Nested Structure

  • Create Person interface
  • Add property descriptions
  • Compile and show output
  • Validate test payloads
  • Create Name interface and extend Person
  • Compile and show output
  • Validate test payloads

Example 3 - Utility Types & Enums

  • Create Employer interface extending Name
  • Add optional employer prop with Pick utility type
  • Compile and show output
  • Validate test payloads
  • Add title prop to Employer as an enum
  • Compile and show output
  • Validate and show output
  • Switch title to use keyof typeof Enum (type literals)
  • Compile and show output
  • Validate test payloads

Example 4 - Property Validation

  • Add email prop to Person with @format email validation
  • Add minLength and maxLength validation to name and location on Employer
  • Add start_date prop to Employer with @format date validation
  • Compile example4 and show output
  • Validate test payloads

About

A walkthrough for generating JSON schemas from TypeScript interfaces.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published