JSON Ecosystem Image

In modern development, JSON is more than a data format—it’s the “contract” that connects front end and back end. From a single JSON sample you can derive type definitions, runtime validation, and API documentation. That is the strength of the JSON ecosystem.

Using one API response as an example, this article connects the flow of format → generate types → validate → define the API with real code.

The Starting Point: One JSON Response

We’ll use this user-info response as our example.

{
  "id": 42,
  "name": "Alice",
  "email": "[email protected]",
  "roles": ["admin"],
  "createdAt": "2026-06-19T10:00:00Z"
}

This single sample is the starting point for everything that follows.

Step 1: Format to Understand the Structure

Real-world JSON often arrives as a single line, making the structure unreadable. First, use the JSON formatter to format it and check the nesting depth, whether values are arrays, and the type of each value. Only after formatting does structure like “is roles an array or a single value?” become clear.

Step 2: Generate the TypeScript Types

Once you understand the structure, create the corresponding TypeScript type.

interface User {
  id: number;
  name: string;
  email: string;
  roles: string[];
  createdAt: string;
}

With the JSON → TypeScript generator, you get this type just by pasting. Now compile-time type checking is in effect.

Step 3: Guarantee Runtime Safety with Zod

A TypeScript type is only a compile-time check. There’s no guarantee an external API actually returns data matching your types—a spec change or bug can break the shape. For runtime validation, prepare a Zod schema.

import { z } from "zod";

const UserSchema = z.object({
  id: z.number(),
  name: z.string(),
  email: z.string().email(),
  roles: z.array(z.string()),
  createdAt: z.string().datetime(),
});

// Derive the type from the schema
type User = z.infer<typeof UserSchema>;

// Validate at the entry point
const user = UserSchema.parse(await res.json());

With z.infer, you derive the TypeScript type from the schema, so you manage types and validation in one place. Invalid data is rejected with an exception right at parse.

Step 4: Make It a Shared Language with OpenAPI

Finally, turning the same JSON into an API spec (OpenAPI / Swagger) schema gives you a “shared language” for front end, back end, QA, and external partners.

components:
  schemas:
    User:
      type: object
      properties:
        id:       { type: integer }
        name:     { type: string }
        email:    { type: string, format: email }
        roles:    { type: array, items: { type: string } }
        createdAt: { type: string, format: date-time }

The JSON → OpenAPI generator gives you a quick first draft of this schema, cutting documentation effort so you can spend time on design discussions.

The Big Picture: One JSON, Many Outputs

        Actual JSON response

   ┌───────────┼───────────────┬───────────────┐
 Format      TS types      Zod schema       OpenAPI
(understand) (static check) (runtime check)  (shared spec)

The key point: these aren’t separate chores but a single flow derived from one sample. Starting from real data prevents drift between docs and implementation, and between types and validation.

Frequently Asked Questions

If I have TypeScript types, do I still need Zod?

Types only work at compile time and don’t validate the data that actually arrives at runtime. For “untrusted input”—external APIs, form input, LocalStorage—pair them with runtime validation like Zod. For trusted internal data, types alone may be enough.

Should I manage data in YAML or JSON?

Machine-to-machine API traffic suits JSON; human-edited config files (CI/CD, Kubernetes, etc.) suit YAML, which allows comments. The two are interconvertible with the JSON ⇔ YAML converter, so use each where it fits.

Is one sample enough to generate types?

A single sample misses optional fields and values that can be null. Use a representative response with as many fields as possible, then add ? or | null by hand to match the spec.