Generate a Zod Schema from JSON and Validate an API Response
A practical JSON-to-Zod workflow: infer nested fields and optional properties, add your application rules, then test both valid and invalid API responses.
Key Architectural Takeaways
- 01.TypeScript describes types at compile time; Zod checks values at runtime.
- 02.Inspect multiple array elements to discover mixed types and optional fields.
- 03.A sample cannot reveal every business rule. Add constraints from your API contract.
- 04.Test invalid payloads as well as successful responses before using the schema.
1. Start with a representative response
Paste a response into the JSON to Zod generator. Include several records when a response contains an array. A single record cannot show whether a field is absent on other records.
In this sample, both users have an id, but only one has a name. The generator makes name optional rather than discarding the second record. JSON is parsed on your device.
{
"users": [
{ "id": 1, "name": "Ada", "active": true },
{ "id": 2, "active": false }
],
"nextPage": null
}2. Review the inferred schema
The generator reads every array element. It combines observed object properties and makes a property optional when it is missing from some objects. Mixed primitive values become unions; an empty array provides no evidence about its element type.
An observed null does not tell us the type of a future non-null value. Here, nextPage is inferred as null. If the API contract says it can also be a string cursor, change it to z.string().nullable().
import { z } from "zod";
export const rootSchema = z.object({
users: z.array(z.object({
id: z.number(),
name: z.string().optional(),
active: z.boolean(),
})),
nextPage: z.null(),
});3. Add the rules the sample cannot express
A number in JSON does not tell us whether negative values are allowed. A string does not tell us whether it is an email address or a fixed set of statuses. Apply the rules specified by your application rather than treating generated code as the complete contract.
For example, replace an id’s z.number() with z.number().int().positive() when the API guarantees a positive integer. Decide whether extra object keys should be stripped, preserved, or rejected using the behavior supported by your installed Zod version.
4. Validate before rendering
Call safeParse after receiving the response and handle its failure result before rendering data. The inferred TypeScript type can then describe validated values.
Try a missing users field, a string id, and a name set to null. Use the mock data generator to explore awkward text and boundary values. These tests should reflect the contract you chose, not just whether the generated schema compiles.
const response = await fetch("/api/users");
if (!response.ok) throw new Error("Could not load users");
const result = rootSchema.safeParse(await response.json());
if (!result.success) {
console.error(result.error.issues);
} else {
console.log(result.data.users);
}
export type UsersResponse = z.infer<typeof rootSchema>;