Capítulo 46 de 61

Chapter 46: JSON Schema — Type-by-Type Conversion Details

Core Idea

Zod's JSON Schema conversion maps each schema type to the JSON Schema mechanism that best represents it — format for well-known string shapes, pattern for the rest, additionalProperties for object strictness, and anyOf/type-arrays for nullability.

Key Concepts

  • String formats: common formats (z.email(), z.iso.datetime(), z.iso.date(), z.iso.duration(), z.ipv4(), z.ipv6(), z.uuid(), z.guid(), z.url()) map to JSON Schema's format keyword; z.base64() maps to contentEncoding; everything else (z.iso.time(), z.base64url(), z.cuid(), z.emoji(), z.nanoid(), z.cuid2(), z.ulid(), z.cidrv4(), z.cidrv6(), z.mac()) falls back to a regex pattern.
  • Numeric types: z.number(){type: "number"}; z.float32()/z.float64() add exclusiveMinimum/exclusiveMaximum bounds; z.int(){type: "integer"}; z.int32() adds the same exclusive bounds.
  • Object strictness maps directly to additionalProperties: plain z.object() emits additionalProperties: false (matching its default strip behavior); z.looseObject() never sets it to false; z.strictObject() always sets it to false. In io: "input" mode, additionalProperties is omitted entirely.
  • z.file(): converts to an OpenAPI-friendly { type: "string", format: "binary", contentEncoding: "binary" }, with .min()/.max()/.mime() adding minLength/maxLength/contentMediaType.
  • Nullability: z.null(){type: "null"}. z.nullable(inner) adds "null" to the type set when inner is a bare type ({type: ["string", "null"]}), or falls back to anyOf when inner isn't representable as a bare type (e.g. it has its own constraints). z.optional() schemas convert to the inner type as-is, annotated as optional rather than changing the JSON Schema shape.

Code Examples

z.email();          // => { type: "string", format: "email" }
z.base64();          // => { type: "string", contentEncoding: "base64" }
z.cuid();             // => { type: "string", pattern: "..." }

z.object({ name: z.string() });
// => { type: "object", properties: {...}, required: [...], additionalProperties: false }

z.file().min(1).max(1024 * 1024).mime("image/png");
// => { type: "string", format: "binary", contentEncoding: "binary",
//      contentMediaType: "image/png", minLength: 1, maxLength: 1048576 }

z.nullable(z.string());          // => { type: ["string", "null"] }
z.nullable(z.string().min(5));   // => { anyOf: [{ type: "string", minLength: 5 }, { type: "null" }] }
  • What it demonstrates: which JSON Schema keyword each Zod construct maps to, and when nullable falls back to anyOf versus a simple type array.

Reference Tables

Zod constructJSON Schema representation
z.email(), z.iso.datetime(), z.url(), etc.format keyword
z.base64()contentEncoding
z.cuid(), z.ulid(), z.mac(), etc.pattern (regex)
z.object() (default)additionalProperties: false
z.looseObject()additionalProperties never false
z.strictObject()additionalProperties always false
z.null(){ type: "null" }

Key Takeaways

  1. When a nullable schema's inner type has its own constraints (length, format, etc.), the output becomes anyOf rather than a simple type array — expect a more verbose JSON Schema in that case.
  2. Object strictness settings translate directly and predictably to additionalProperties — a quick way to audit whether a generated JSON Schema will reject unknown properties.
  3. Not every string format gets a dedicated JSON Schema format value — many fall back to a pattern regex, which is still functionally correct but less semantically labeled.

Connects To

  • JSON Schema — Conversion Functions: the z.toJSONSchema() entry point and its options.
  • JSON Schema — Conversion Details & Registries: handling schema reuse, cycles, and $defs.