Convert JSON:API Spec Responses to TypeScript

Use json to typescript to model the JSON:API specification — generic data, included relationships, links, and self-describing resource types.

Real-World API Schemas

Detailed Explanation

JSON:API as a Generic Type

The JSON:API spec standardizes envelope, relationships, and pagination. A single generic captures the entire structure regardless of which resource is returned.

Example JSON

{
  "data": {
    "type": "articles",
    "id": "1",
    "attributes": { "title": "TypeScript Wins", "published": true },
    "relationships": {
      "author": { "data": { "type": "people", "id": "9" } }
    }
  },
  "included": [
    {
      "type": "people",
      "id": "9",
      "attributes": { "name": "Dan", "twitter": "@dan" }
    }
  ],
  "links": { "self": "/articles/1" }
}

Generated TypeScript

interface ResourceIdentifier {
  type: string;
  id: string;
}

interface Resource<TType extends string, TAttrs, TRel = Record<string, never>> {
  type: TType;
  id: string;
  attributes: TAttrs;
  relationships?: { [K in keyof TRel]: { data: ResourceIdentifier | ResourceIdentifier[] } };
  links?: { self?: string };
}

interface JsonApiDocument<TPrimary, TIncluded extends Resource<string, unknown>[] = Resource<string, unknown>[]> {
  data: TPrimary | TPrimary[];
  included?: TIncluded;
  links?: { self?: string; next?: string; prev?: string };
}

type Article = Resource<"articles", { title: string; published: boolean }, { author: unknown }>;
type Person = Resource<"people", { name: string; twitter: string }>;

type ArticleDocument = JsonApiDocument<Article, Person[]>;

Why This Shape Works

JSON:API splits resource attributes from relationships, and the spec guarantees type and id exist on every resource. By making type a literal parameter, the resulting union narrows correctly when you walk included:

function findAuthor(doc: ArticleDocument): Person | undefined {
  return doc.included?.find((r): r is Person => r.type === "people");
}

Conversion Tips

  • Always type id as string — JSON:API spec requires string IDs even when the underlying database uses integers.
  • Use Resource<TType, TAttrs> for read-only paths and a separate ResourceCreate (without id) for POST payloads.
  • The included array is heterogeneous, so type it as a union of all possible resources you embed, not as unknown[].

This generic envelope works for Drupal, Ember Data, OpenStack, and any other JSON:API producer with no per-endpoint type duplication.

Use Case

Building a JavaScript client for a Drupal-backed CMS that returns content via JSON:API and you need typed access to embedded relationships in the included array.

Try It — JSON to TypeScript

Open full tool