DEV Community

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

Posted on • Edited on

4

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)

This post blew up on DEV in 2020:

js visualized

🚀⚙️ JavaScript Visualized: the JavaScript Engine

As JavaScript devs, we usually don't have to deal with compilers ourselves. However, it's definitely good to know the basics of the JavaScript engine and see how it handles our human-friendly JS code, and turns it into something machines understand! 🥳

Happy coding!

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay