Quick answer
A Zod schema validates data at runtime and gives you its TypeScript type for free through z.infer. Paste a JSON sample into Playxoft's free JSON to Zod generator to get a schema for every object: missing keys become .optional(), null values become .nullable(), whole numbers get .int(), and ISO dates get date-time validation. Choose Zod 3 or Zod 4 syntax, then call parse or safeParse on untrusted data.
- Zod 3 and Zod 4 syntax
- z.infer type exports
- Optional, nullable, nullish
- Integer and date-time checks
- Email, URL and UUID formats
- Dependency-ordered schemas
- Download schema.ts
How to use the JSON to Zod
- 1Paste a real API response into JSON sample.
- 2Name the root schema with Root type name:
UserbecomesUserSchemaandtype User. - 3Keep Zod 4 (the default) or pick Zod 3, and toggle Validate dates, Integers, Emails, URLs, UUIDs and Strict objects.
- 4Copy the code or download schema.ts, and install Zod with
npm install zod(which installs Zod 4). - 5Validate data with
UserSchema.safeParse(data)and handle the failure case.
Why use Zod instead of plain TypeScript types?
TypeScript checks your code, not your data. An interface for an API response is a promise the compiler can't verify, because the response only exists at runtime. Zod closes that gap: you describe the shape once, Zod validates real data against it, and z.infer derives the static type, so the type and the check can never drift apart.
import { z } from "zod";
// Zod 4. In Zod 3: z.number().int(), z.string().email(), z.string().datetime()
const UserSchema = z.object({
id: z.int(),
email: z.email(),
createdAt: z.iso.datetime(),
});
type User = z.infer<typeof UserSchema>;
const user = UserSchema.parse(await res.json()); // typed and checkedUse it wherever untrusted data enters your program: fetch responses, form input, webhooks, message queues, environment variables and localStorage.
How does JSON map to Zod?
| JSON in your sample | Zod 3 | Zod 4 |
|---|---|---|
| String | z.string() | z.string() |
| ISO 8601 date-time | z.string().datetime() | z.iso.datetime() |
| Whole number | z.number().int() | z.int() |
| Decimal number | z.number() | z.number() |
| Boolean | z.boolean() | z.boolean() |
| Array | z.array(T) | z.array(T) |
| Object | z.object({...}) | z.object({...}) |
| Empty or ID-keyed object | z.record(z.string(), T) | z.record(z.string(), T) |
| Mixed value types | z.union([A, B]) | z.union([A, B]) |
Schemas are written in dependency order, so each one is declared before another schema uses it, and every schema gets a matching export type. When a date-time in your sample carries a UTC offset such as +05:30, the generator adds { offset: true }, because by default Zod only accepts the Z suffix.
optional, nullable or nullish: what's the difference?
| Modifier | Accepts | Generated when |
|---|---|---|
.optional() | The value, or a missing key | The key is missing from some samples |
.nullable() | The value, or null | The value is null in some samples |
.nullish() | The value, null or a missing key | Both of the above |
Choose these deliberately. An API that sends "middleName": null and one that leaves middleName out are different contracts, and Zod rejects the one you didn't declare. That is exactly what you want from a validator: a clear error at the boundary instead of an undefined deep inside your app.
What changed in Zod 4?
Zod 4, released in 2025, keeps the familiar API but moves string formats to the top level and tightens a few defaults. The generator writes this style by default:
z.email(),z.url(),z.uuid()andz.iso.datetime()replacez.string().email()and friends, which still work but are deprecated.z.int()only accepts safe integers, so an ID above 2^53 fails validation. The generator falls back toz.number()for such fields, in both versions.z.strictObject()andz.looseObject()replace the.strict()and.passthrough()methods.
Choose Zod 3 for a project still on Zod 3, or for shared code that must run on both: the Zod 3 syntax this generator writes runs on both major versions. Either way, z.object() strips unknown keys from the parsed result by default; turn on Strict objects to reject them instead.
How should I handle validation errors?
parse() throws a ZodError, while safeParse() returns a result you branch on, which keeps control flow explicit:
const result = UserSchema.safeParse(data);
if (!result.success) {
console.error(result.error.issues); // each issue has a path and a message
return;
}
const user = result.data; // fully typedEach issue carries a path to the failing field, which maps straight onto form fields and log messages. Form libraries such as React Hook Form (through @hookform/resolvers) and API frameworks such as tRPC accept Zod schemas directly. Need plain types without runtime checks? The JSON to TypeScript generator writes interfaces from the same sample.
Frequently asked questions
Last updated by the Playxoft engineering team.