Skip to content

JSON Schema to Zod Schema Transformer — Numio

Generates Zod validation code from a JSON Schema with json-schema-to-zod. Constraints such as minLength, minimum, pattern and default become chained Zod methods, and properties not listed in "required" get .optional().

You receive a JSON Schema from another team and want runtime validation in a TypeScript service without a JSON Schema validator.

JSON Schema

Zod Schema

When to use it

  • You receive a JSON Schema from another team and want runtime validation in a TypeScript service without a JSON Schema validator.
  • You are moving form validation from a JSON Schema validator such as Ajv to Zod and want generated code to start from.

How to use

  1. Paste a JSON Schema into the JSON Schema panel.
  2. Optionally open settings and change Root Schema Name (default "schema"); it becomes the name of the exported const.
  3. Copy the generated code, which imports z from "zod", into your project. Conversion runs in the browser.

JSON Schema

{
  "type": "object",
  "properties": {
    "username": { "type": "string", "minLength": 3, "maxLength": 20 },
    "age": { "type": "integer", "minimum": 18 },
    "contact": {
      "oneOf": [
        { "type": "string", "format": "email" },
        { "type": "string", "pattern": "^\\+[0-9]+$" }
      ]
    },
    "newsletter": { "type": "boolean", "default": false }
  },
  "required": ["username", "contact"]
}

Zod schema

import { z } from "zod"

export const schema = z.object({
  username: z.string().min(3).max(20),
  age: z.number().int().gte(18).optional(),
  contact: z.any().superRefine((x, ctx) => {
    const schemas = [
      z.string().email(),
      z.string().regex(new RegExp("^\\+[0-9]+$")),
    ]
    const errors = schemas.reduce(
      (errors: z.ZodError[], schema) =>
        ((result) => ("error" in result ? [...errors, result.error] : errors))(
          schema.safeParse(x),
        ),
      [],
    )
    if (schemas.length - errors.length !== 1) {
      ctx.addIssue({
        path: ctx.path,
        code: "invalid_union",
        unionErrors: errors,
        message: "Invalid input: Should pass single schema",
      })
…

Details and limitations

oneOf is converted to z.any().superRefine(...), which checks that exactly one sub-schema passes, as the example shows.

$ref is not resolved: a referenced property becomes z.any().

integer adds .int(), format "email" adds .email() and format "date-time" adds .datetime().

additionalProperties: false becomes .catchall(z.never()) instead of .strict().

FAQ

How are nullable types handled?

"type": ["string", "null"] becomes z.union([z.string(), z.null()]), with .optional() added if the property is not required.

Can I rename the exported schema?

Yes. Set Root Schema Name in the settings; the output then reads export const <name> = ... instead of export const schema.

Are definitions and $ref supported?

No. A property using $ref becomes z.any(), so inline the referenced schema before converting.

Specifications and references