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.
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
idasstring— JSON:API spec requires string IDs even when the underlying database uses integers. - Use
Resource<TType, TAttrs>for read-only paths and a separateResourceCreate(withoutid) for POST payloads. - The
includedarray is heterogeneous, so type it as a union of all possible resources you embed, not asunknown[].
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
Related Topics
Convert GraphQL JSON Responses to TypeScript
Real-World API Schemas
Create Generic TypeScript Wrapper Types from JSON Envelopes
Complex Types
Convert Stripe Charge JSON to TypeScript Interfaces
Real-World API Schemas
Convert GitHub Repository API JSON to TypeScript
Real-World API Schemas
JSON to TypeScript Discriminated Union — Result/Error Pattern
Real-World API Schemas