DEV Community

Cover image for Unlocking API Interoperability: Converting OpenAPI to TypeScript JSON Schema
Andrea Carraro
Andrea Carraro

Posted on • Updated on

Unlocking API Interoperability: Converting OpenAPI to TypeScript JSON Schema

Intro

This post briefly tells the story of why I wrote openapi-ts-json-schema to fill the unexpected gap existing between OpenAPI specification and TypeScript JSON schema format.

Prologue

In the last few weeks at work I was faced with an obvious task which I thought had an obvious technical solution, from my naive fronted engineer background:

Given an OpenAPI Specification, generate the relevant JSON schema files to be consumed and type-interpreted by a TypeScript application.

The main idea consists of leveraging a library like json-schema-to-ts to infer TypeScript types from the generated JSON schema files. This is a quite common pattern documented by Ajv and Fastify, to name a few:

import Ajv from 'ajv';
import type { FromSchema } from 'json-schema-to-ts';
import mySchema from 'path/to/generated/schemas/MyModel.ts';

const ajv = new Ajv();
// Perform data validation and type inference using the same schema
const validate = ajv.compile<FromSchema<typeof mySchema>>(mySchema);
const data: unknown = {};

if (validate(data)) {
  // data gets type inference
  console.log(data.foo);
} else {
  console.log(validate.errors);
}
Enter fullscreen mode Exit fullscreen mode

Since the OpenAPI Specification is a "machine-readable interface definition language for describing, [...] web services", and TypeScript is practically the only available type solution for Node.js, I confidently googled:

"openAPI to typescript JSON schema"

...and found no relevant results.

OpenAPI to TypeScript JSON schema gap

If OpenAPI -> TypeScript types is mostly covered by openapi-typescript and OpenAPI -> JSON schema is covered by openapi-schema-to-json-schema, there is no clear way to generate TypeScript JSON schemas from an OpenAPI definition.

The main shortcomings consist of TypeScript not being able to:

const dogSchema = {
  type: "object",
  properties: {
    name: { type: "string" },
    // TS cannot interpret/resolve $refs
    favoriteFood: { "$ref": "components/schemas/Food" },
  },
  required: ["name", "favoriteFood"],
} as const;
Enter fullscreen mode Exit fullscreen mode

Considering the example above, in order to have a JSON schema which TypeScript can properly interpret, all $ref entries should be somehow statically resolved at generation time.

After an initial attempt to manually replace the $refs all around my JSON schemas, I ended up putting together a few lines to automate the process which I eventually abstracted into a JS NPM package: openapi-ts-json-schema.

The generation flow consists of:

  1. Resolve external/remote $refs and dereference them with @apidevtools/json-schema-ref-parser (resolving $ref's)
  2. Convert to JSON schema with @openapi-contrib/openapi-schema-to-json-schema and openapi-jsonschema-parameters
  3. Generate one TypeScript JSON schema file for each definition (.ts files with as const assertion)
  4. Store schemas in a folder structure reflecting the original OpenAPI definition structure

The example above would result into a TypeScript JSON schema file, ready to be imported and consumed:

import componentsSchemasFood from "./../components/schemas/Food"

export default {
  type: "object",
  properties: {
    name: { type: "string" },
    favoriteFood: componentsSchemasFood,
  },
  required: ["name", "favoriteFood"],
} as const;
Enter fullscreen mode Exit fullscreen mode

Since v0.3.0, $ref definitions get generated as standalone TS JSON schema files and imported where needed.

Conclusion

Given the centrality of JSON schemas in most of web development, I would be glad to see TypeScript taking over and make JSON schemas first class citizens by adding native support for:

  • importing JSON schema import as const
  • inferring types natively from any JSON schema (currently achieved with the outstanding json-schema-to-ts)

TypeScript JSON schemas are 100% valid JSON schemas and would be able to represent the missing joining link between runtime type validation and static type check.

In the meanwhile feel free to take a look at the approach taken by openapi-ts-json-schema and start a discussion there in case something doesn't fit your use case.

Top comments (0)