@traversable/schema
    Preparing search index...

    Module @traversable/zod - v0.0.57


    ᯓ𝘁𝗿𝗮𝘃𝗲𝗿𝘀𝗮𝗯𝗹𝗲/𝘇𝗼𝗱


    @traversable/zod or zx is a schema rewriter for zod.

    NPM Version   TypeScript   License   npm  
    Static Badge   Static Badge   Static Badge  


    @traversable/zod has a peer dependency on Zod (v4).

    Read the blog post, Introducing: @traversable/zod (3 min read).

    $ pnpm add @traversable/zod zod
    

    Here's an example of importing the library:

    import { z } from 'zod'
    import { zx } from '@traversable/zod'

    // see below for specific examples

    zx.check converts a zod-schema into a super-performant type-guard.

    • Better performance than z.parse and z.safeParse
    • Works in any environment that supports defining functions using the Function constructor, including (as of May 2025) Cloudflare workers 🎉

    Here's a Bolt sandbox if you'd like to run the benchmarks yourself.

    z.parse and z.safeParse clone the object they're parsing, and return an array of issues if any are encountered.

    Those features are useful in certain contexts.

    But in contexts where all you need is to know whether a value is valid or not, it'd be nice to have a faster alternative, that doesn't allocate.

    zx.check takes a zod schema, and returns a type guard. It's performance is an order of magnitude faster than z.parse and z.safeParse in almost every case.

                         ┌─────────────────┐
    Average
    ┌────────────────────┼─────────────────┤
    z.parse (v4) │ 20.41x faster
    ├────────────────────┼─────────────────┤
    z.safeParse (v4) │ 21.05x faster
    └────────────────────┴─────────────────┘
    import { z } from 'zod'
    import { zx } from '@traversable/zod'

    const Address = z.object({
    street1: z.string(),
    street2: z.optional(z.string()),
    city: z.string(),
    })

    const addressCheck = zx.check(Address)

    addressCheck({ street1: '221B Baker St', city: 'London' }) // => true
    addressCheck({ street1: '221B Baker St' }) // => false

    zx.check converts a zod-schema into a super-performant type-guard.

    Compared to zx.check, zx.check.writeable returns the check function in stringified ("writeable") form.

    • Useful when you're consuming a set of zod schemas and writing them all to disc
    • Also useful for testing purposes or for troubleshooting, since it gives you a way to "see" exactly what the check functions check
    • Since you're presumably writing to disc a build-time, works with Cloudflare workers
    import { z } from 'zod'
    import { zx } from '@traversable/zod'

    const addressCheck = zx.check.writeable(
    z.object({
    street1: z.string(),
    street2: z.optional(z.string()),
    city: z.string(),
    }),
    { typeName: 'Address' }
    )

    console.log(addressCheck)
    // =>
    // type Address = { street1: string; street2?: string; city: string; }
    // function check(value: Address) {
    // return (
    // !!value &&
    // typeof value === "object" &&
    // typeof value.street1 === "string" &&
    // (!Object.hasOwn(value, "street2") || typeof value?.street2 === "string") &&
    // typeof value.city === "string"
    // );
    // }

    zx.deepClone lets users derive a specialized "deep copy" function that works with values that have been already validated.

    Because the values have already been validated, clone times are significantly faster than alternatives like window.structuredClone and Lodash.cloneDeep.

    Here's a Bolt sandbox if you'd like to run the benchmarks yourself.

                               ┌─────────────────┐
    │ (avg) │
    ┌──────────────────────────┼─────────────────┤
    Lodash.cloneDeep │ 30.64x faster
    ├──────────────────────────┼─────────────────┤
    window.structuredClone │ 50.26x faster
    └──────────────────────────┴─────────────────┘

    This article goes into more detail about what makes zx.deepClone so fast.

    import { assert } from 'vitest'
    import { z } from 'zod'
    import { zx } from '@traversable/zod'

    const Address = z.object({
    street1: z.string(),
    street2: z.optional(z.string()),
    city: z.string(),
    })

    const clone = zx.deepClone(Address)

    const sherlock = { street1: '221 Baker St', street2: '#B', city: 'London' }
    const harry = { street1: '4 Privet Dr', city: 'Little Whinging' }

    const sherlockCloned = clone(sherlock)
    const harryCloned = clone(harry)

    // values are deeply equal:
    assert.deepEqual(sherlockCloned, sherlock) // ✅
    assert.deepEqual(harryCloned, harry) // ✅

    // values are fresh copies:
    assert.notEqual(sherlockCloned, sherlock) // ✅
    assert.notEqual(harryCloned, harry) // ✅

    zx.deepClone lets users derive a specialized "deep clone" function that works with values that have been already validated.

    Compared to zx.deepClone, zx.deepClone.writeable returns the clone function in stringified ("writeable") form.

    import { z } from 'zod'
    import { zx } from '@traversable/zod'

    const Address = z.object({
    street1: z.string(),
    street2: z.optional(z.string()),
    city: z.string(),
    })

    const deepClone = zx.deepClone.writeable(
    z.object({
    street1: z.string(),
    street2: z.optional(z.string()),
    city: z.string(),
    }),
    { typeName: 'Address' }
    )

    console.log(deepClone)
    // =>
    // type Address = { street1: string; street2?: string; city: string; }
    // function deepClone(prev: Address) {
    // return {
    // street1: prev.street1,
    // ...prev.street2 !== undefined && { street2: prev.street2 },
    // city: prev.city
    // }
    // }

    zx.deepEqual lets users derive a specialized "deep equal" function that works with values that have been already validated.

    Because the values have already been validated, comparison times are significantly faster than alternatives like NodeJS.isDeepStrictEqual and Lodash.isEqual.

    Here's a Bolt sandbox if you'd like to run the benchmarks yourself.

                                 ┌────────────────┬────────────────┐
    Array (avg) │ Object (avg) │
    ┌────────────────────────────┼────────────────┼────────────────┤
    NodeJS.isDeepStrictEqual │ 40.3x faster │ 56.5x faster
    ├────────────────────────────┼────────────────┼────────────────┤
    Lodash.isEqual │ 53.7x faster │ 60.1x faster
    └────────────────────────────┴────────────────┴────────────────┘

    This article goes into more detail about what makes zx.deepEqual so fast.

    • Works in any environment that supports defining functions using the Function constructor, including (as of May 2025) Cloudflare workers 🎉
    import { z } from 'zod'
    import { zx } from '@traversable/zod'

    const deepEqual = zx.deepEqual(
    z.object({
    street1: z.string(),
    street2: z.optional(z.string()),
    city: z.string(),
    })
    )

    deepEqual(
    { street1: '221B Baker St', city: 'London' },
    { street1: '221B Baker St', city: 'London' }
    ) // => true

    deepEqual(
    { street1: '221B Baker St', city: 'London' },
    { street1: '4 Privet Dr', city: 'Little Whinging' }
    ) // => false
    • Useful when you're consuming a set of zod schemas and writing them all to disc
    • Also useful for testing purposes or for troubleshooting, since it gives you a way to "see" exactly what the deep equal functions are doing
    import { z } from 'zod'
    import { zx } from '@traversable/zod'

    const deepEqual = zx.deepEqual.writeable(
    z.object({
    street1: z.string(),
    street2: z.optional(z.string()),
    city: z.string(),
    }),
    { typeName: 'Address' }
    )

    console.log(deepEqual)
    // =>
    // type Address = { street1: string; street2?: string; city: string; }
    // function deepEqual(x: Address, y: Address) {
    // if (x === y) return true;
    // if (x.street1 !== y.street1) return false;
    // if (x.street2 !== y.street2) return false;
    // if (x.city !== y.city) return false;
    // return true;
    // }
    • This option is provided as a fallback in case users cannot work with either #1 or #2
    import { z } from 'zod'
    import { zx } from '@traversable/zod'
    import * as vi from 'vitest'

    const deepEqual = zx.deepEqual.classic(
    z.object({
    street1: z.string(),
    street2: z.optional(z.string()),
    city: z.string(),
    })
    )

    deepEqual(
    { street1: '221B Baker St', city: 'London' },
    { street1: '221B Baker St', city: 'London' },
    ) // => true

    deepEqual(
    { street1: '221B Baker St', city: 'London' },
    { street1: '4 Privet Dr', city: 'Little Whinging' },
    ) // => false

    Convert a Zod schema into a codec that applies a bi-directional key transformation to all object schemas recursively.

    Note

    You can play with this example on StackBlitz

    import * as z from 'zod'
    import { zx } from '@traversable/zod'

    const createAllCapsCodec = zx.convertCaseCodec({
    decodeKeys: (k) => k.toUpperCase(),
    encodeKeys: (k) => k.toLowerCase(),
    })

    const ALL_CAPS = createAllCapsCodec(
    z.object({
    abc: z.string(),
    def: z.object({
    ghi: z.array(
    z.object({
    jkl: z.boolean()
    })
    )
    })
    })
    )

    console.log(
    ALL_CAPS.decode({
    abc: 'hi how are you',
    def: {
    ghi: [
    { jkl: false },
    { jkl: true },
    ]
    }
    })
    )
    /* {
    ABC: "hi how are you",
    DEF: {
    GHI: [
    { "JKL": false },
    { "JKL": true }
    ]
    }
    } */

    console.log(
    ALL_CAPS.encode({
    ABC: "hi how are you",
    DEF: {
    GHI: [
    { "JKL": false },
    { "JKL": true }
    ]
    }
    })
    )
    /* {
    abc: 'hi how are you',
    def: {
    ghi: [
    { jkl: false },
    { jkl: true },
    ]
    }
    } */
    Warning

    Support for this feature is experimental (🔬).

    Convert a Zod schema into a codec that decodes any objects's keys to camel case and encode any object's keys to snake case, recursively.

    Note

    This feature was implemented in terms of zx.convertCaseCodec.

    Note

    You can play with this example on StackBlitz

    import * as z from 'zod'
    import { zx } from '@traversable/zod'

    const CAMEL = zx.deepCamelCaseCodec(
    z.object({
    abc_def: z.string(),
    ghi_jkl: z.object({
    mno_pqr: z.number(),
    stu_vwx: z.array(
    z.object({
    y_z: z.boolean()
    })
    )
    })
    })
    )

    console.log(
    CAMEL.decode({
    abc_def: 'hi how are you',
    ghi_jkl: {
    mno_pqr: 123,
    stu_vwx: [
    {
    y_z: true
    },
    {
    y_z: false
    }
    ]
    }
    })
    )
    /* {
    abcDef: "hi how are you",
    ghiJkl: {
    mnoPqr: 123,
    stuVwx: [
    {
    yZ: true,
    },
    {
    yZ: false,
    },
    ],
    },
    } */

    console.log(
    CAMEL.encode({
    abcDef: "hi how are you",
    ghiJkl: {
    mnoPqr: 123,
    stuVwx: [
    {
    yZ: true,
    },
    {
    yZ: false,
    },
    ],
    },
    })
    )
    /* {
    abc_def: 'hi how are you',
    ghi_jkl: {
    mno_pqr: 123,
    stu_vwx: [
    {
    y_z: true
    },
    {
    y_z: false
    }
    ]
    }
    } */
    Warning

    Support for this feature is experimental (🔬).

    Convert a Zod schema into a codec that decodes any objects's keys to snake case and encode any object's keys to camel case, recursively.

    Note

    This feature was implemented in terms of zx.convertCaseCodec.

    Note

    You can play with this example on StackBlitz

    import * as z from 'zod'
    import { zx } from '@traversable/zod'

    const SNAKE = zx.deepSnakeCaseCodec(
    z.object({
    abc_def: z.string(),
    ghi_jkl: z.object({
    mno_pqr: z.number(),
    stu_vwx: z.array(
    z.object({
    y_z: z.boolean()
    })
    )
    })
    })
    )

    console.log(
    SNAKE.decode({
    abcDef: "hi how are you",
    ghiJkl: {
    mnoPqr: 123,
    stuVwx: [
    {
    yZ: true,
    },
    {
    yZ: false,
    },
    ],
    },
    })
    )
    /* {
    abc_def: 'hi how are you',
    ghi_jkl: {
    mno_pqr: 123,
    stu_vwx: [
    {
    y_z: true
    },
    {
    y_z: false
    }
    ]
    }
    } */

    console.log(
    SNAKE.encode({
    abc_def: 'hi how are you',
    ghi_jkl: {
    mno_pqr: 123,
    stu_vwx: [
    {
    y_z: true
    },
    {
    y_z: false
    }
    ]
    }
    })
    )
    /* {
    abcDef: "hi how are you",
    ghiJkl: {
    mnoPqr: 123,
    stuVwx: [
    {
    yZ: true,
    },
    {
    yZ: false,
    },
    ],
    },
    } */

    Convert a blob of JSON data into a zod schema that represents its least upper bound.

    import { zx } from '@traversable/zod'

    let example = zx.fromConstant({ abc: 'ABC', def: [1, 2, 3] })
    // ^? let example: z.ZodType<{ abc: 'ABC', def: [1, 2, 3] }>

    console.log(zx.toString(example))
    // => z.object({ abc: z.literal("ABC"), def: z.tuple([ z.literal(1), z.literal(2), z.literal(3) ]) })

    Convert a blob of JSON data into a stringified zod schema that represents its least upper bound.

    import { zx } from '@traversable/zod'

    let ex_01 = zx.fromConstant.writeable({ abc: 'ABC', def: [1, 2, 3] })

    console.log(ex_01)
    // => z.object({ abc: z.literal("ABC"), def: z.tuple([ z.literal(1), z.literal(2), z.literal(3) ]) })

    Convert a blob of JSON data into a zod schema that represents its greatest lower bound.

    import type { z } from 'zod'
    import { zx } from '@traversable/zod'

    let ex_01 = zx.fromJson({ abc: 'ABC', def: [] })
    // ^? let ex_01: z.ZodObject<{ abc: z.ZodString, def: z.ZodArray<z.ZodUnknown> }>

    console.log(zx.toString(ex_01))
    // => z.object({ abc: z.string(), def: z.array(z.unknown()) })

    let ex_02 = zx.fromJson({ abc: 'ABC', def: [123] })
    // ^? let ex_01: z.ZodObject<{ abc: z.ZodString, def: z.ZodArray<z.ZodUnknown> }>

    console.log(zx.toString(ex_02))
    // => z.object({ abc: z.string(), def: z.array(z.number()) })

    let ex_03 = zx.fromJson({ abc: 'ABC', def: [123, null]})
    // ^? let ex_01: z.ZodObject<{ abc: z.ZodString, def: z.ZodArray<z.Union<[z.ZodNumber, z.ZodNull]>> }>

    console.log(zx.toString(ex_03))
    // => z.object({ abc: z.string(), def: z.array(z.union([z.number(), z.null()])) })

    Convert a blob of JSON data into a stringified Zod schema that represents its greatest lower bound.

    import type { z } from 'zod'
    import { zx } from '@traversable/zod'

    let ex_01 = zx.fromJson.writeable({ abc: 'ABC', def: [] })

    console.log(ex_01)
    // => z.object({ abc: z.string(), def: z.array(z.unknown()) })

    let ex_02 = zx.fromJson.writeable({ abc: 'ABC', def: [123] })

    console.log(ex_02)
    // => z.object({ abc: z.string(), def: z.array(z.number()) })

    let ex_03 = zx.fromJson.writeable({ abc: 'ABC', def: [123, null]})

    console.log(ex_03)
    // => z.object({ abc: z.string(), def: z.array(z.union([z.number(), z.null()])) })

    Credit goes to @jaens for their work to detect circular schemas and prevent stack overflow.

    import { z } from 'zod'
    import { zx } from '@traversable/zod'

    const MySchema = zx.deepPartial(z.object({ a: z.number(), b: z.object({ c: z.string() }) }))

    type MySchema = z.infer<typeof MySchema>
    // ^? type MySchema = { a?: number, b?: { c?: string } }
    import { z } from 'zod'
    import { zx } from '@traversable/zod'

    const MySchema = z.object({ a: z.number(), b: z.object({ c: z.string() }) })

    console.log(zx.deepPartial.writeable(MySchema))
    // =>
    // z.object({
    // a: z.number().optional(),
    // b: z.object({
    // c: z.string().optional(),
    // d: z.array(z.boolean()).optional()
    // }).optional()
    // }).optional()

    zx.defaultValues converts a Zod schema into a "default value' that respects the structure of the schema.

    A common use case for zx.defaultValue is creating default values for forms.

    Note

    By default, zx.defaultValue does not make any assumptions about what "default" means for primitive types, which is why it returns undefined when it encounters a leaf value. This behavior is configurable.

    import { z } from 'zod'
    import { zx } from '@traversable/zod'

    const MySchema = z.object({
    a: z.number(),
    b: z.object({
    c: z.string(),
    d: z.array(z.boolean())
    })
    })

    // by default, primitives are initialized as `undefined`:
    const defaultOne = zx.defaultValue(MySchema)
    console.log(defaultOne) // => { a: undefined, b: { c: undefined, d: [] } }

    // to configure this behavior, use the `fallbacks` property:
    const defaultTwo = zx.defaultValue(MySchema, { fallbacks: { number: 0, string: '' } })
    console.log(defaultTwo) // => { a: 0, b: { c: '', d: [] } }

    zx.toPaths converts a zod schema into an array of "paths" that represent the schema.

    import { z } from 'zod'
    import { zx } from '@traversable/zod'

    console.log(
    zx.toPaths(z.object({ a: z.object({ c: z.string() }), b: z.number() }))
    ) // => [["a", "c"], ["b"]]

    Convert a Zod schema into a string that constructs the same zod schema.

    Useful for writing/debugging tests that involve randomly generated schemas.

    import { z } from 'zod'
    import { zx } from '@traversable/zod'

    console.log(
    zx.toString(
    z.templateLiteral([1n])
    )
    ) // => z.templateLiteral([1n])

    console.log(
    zx.toString(
    z.map(z.array(z.boolean()), z.set(z.number().optional()))
    )
    ) // => z.map(z.array(z.boolean()), z.set(z.number().optional()))

    console.log(
    zx.toString(
    z.tuple([
    z.number().min(0).lt(2),
    z.number().multipleOf(2).nullable(),
    ])
    )
    ) // => z.tuple([z.number().min(0).lt(2), z.number().multipleOf(2).nullable()])

    Convert a Zod schema into a string that represents its type.

    To preserve JSDoc annotations for object properties, pass preserveJsDocs: true in the options object. If the property's metadata includes an example property, the example will be escaped and included as an @escape tag.

    Note

    By default, the type will be returned as an "inline" type. To give the type a name, use the typeName option.

    import { z } from 'zod'
    import { zx } from '@traversable/zod'

    console.log(
    zx.toType(
    z.object({
    a: z.optional(z.literal(1)),
    b: z.literal(2),
    c: z.optional(z.literal(3))
    })
    )
    ) // => { a?: 1, b: 2, c?: 3 }

    console.log(
    zx.toType(
    z.intersection(
    z.object({ a: z.literal(1) }),
    z.object({ b: z.literal(2) })
    )
    )
    ) // => { a: 1 } & { b: 2 }

    console.log(
    zx.toType(
    z.templateLiteral([
    z.literal(['a', 'b']),
    ' ',
    z.literal(['c', 'd']),
    ' ',
    z.literal(['e', 'f'])
    ])
    )
    ) // => "a c e" | "a c f" | "a d e" | "a d f" | "b c e" | "b c f" | "b d e" | "b d f"

    // To give the generated type a name, use the `typeName` option:
    console.log(
    zx.toType(
    z.object({ a: z.optional(z.number()) }),
    { typeName: 'MyType' }
    )
    ) // => type MyType = { a?: number }

    // To preserve JSDoc annotations, use the `preserveJsDocs` option:
    console.log(
    zx.toType(
    z.object({
    street1: z.string().meta({ describe: 'Street 1 name' }),
    street2: z.string().optional().meta({ describe: 'Street 2 name', example: 'Unit B' }),
    city: z.string(),
    }),
    { typeName: 'Address', preserveJsDocs: true }
    )
    )
    // =>
    // type Address = {
    // /**
    // * Street 1 name
    // */
    // street1: string
    // /**
    // * Street 2 name
    // * @example "Unit B"
    // */
    // street2?: string
    // city: string
    // }

    Recursively removes any z.default nodes.

    Unless you opt out, if the node is an object property, the property will be wrapped with z.optional.

    To opt out, pass { replaceWithOptional: false } as the second argument to zx.deepNoDefaults.

    import { z } from 'zod'
    import { zx } from "@traversable/zod"

    const withoutDefaults = zx.deepNoDefaults(
    z.object({
    a: z.number().default(0),
    b: z.boolean().default(false).optional(),
    c: z.boolean().optional().default(false),
    d: z.union([z.string().default(''), z.number().default(0)]),
    e: z.array(
    z.object({
    f: z.number().default(0),
    g: z.boolean().default(false).optional(),
    h: z.boolean().optional().default(false),
    i: z.union([z.string().default(''), z.number().default(0)]),
    }).default({
    f: 0,
    g: false,
    h: false,
    i: '',
    })
    ).default([])
    })
    )

    console.log(
    zx.toString(withoutDefaults)
    )
    // =>
    // z.object({
    // a: z.number().optional(),
    // b: z.boolean().optional(),
    // c: z.boolean().optional(),
    // d: z.union([z.string(), z.number()]).optional(),
    // e: z
    // .array(
    // z.object({
    // f: z.number().optional(),
    // g: z.boolean().optional(),
    // h: z.boolean().optional(),
    // i: z.union([z.string(), z.number()]).optional(),
    // }),
    // )
    // .optional(),
    // })

    Recursively removes any z.default nodes, and returns the transformed schema in string form.

    Unless you opt out, if the node is an object property, the property will be wrapped with z.optional.

    To opt out, pass { replaceWithOptional: false } as the second argument to zx.deepNoDefaults.

    import { z } from 'zod'
    import { zx } from "@traversable/zod"

    const withoutDefaults = zx.deepNoDefaults.writeable(
    z.object({
    a: z.number().default(0),
    b: z.boolean().default(false).optional(),
    c: z.boolean().optional().default(false),
    d: z.union([z.string().default(''), z.number().default(0)]),
    e: z.array(
    z.object({
    f: z.number().default(0),
    g: z.boolean().default(false).optional(),
    h: z.boolean().optional().default(false),
    i: z.union([z.string().default(''), z.number().default(0)]),
    }).default({
    f: 0,
    g: false,
    h: false,
    i: '',
    })
    ).default([])
    })
    )

    console.log(withoutDefaults)
    // =>
    // z.object({
    // a: z.number().optional(),
    // b: z.boolean().optional(),
    // c: z.boolean().optional(),
    // d: z.union([z.string(), z.number()]).optional(),
    // e: z
    // .array(
    // z.object({
    // f: z.number().optional(),
    // g: z.boolean().optional(),
    // h: z.boolean().optional(),
    // i: z.union([z.string(), z.number()]).optional(),
    // }),
    // )
    // .optional(),
    // })
    import { z } from 'zod'
    import { zx } from '@traversable/zod'

    const MySchema = zx.deepLoose(
    z.object({
    a: z.number(),
    b: z.object({
    c: z.string()
    })
    })
    )
    import { z } from 'zod'
    import { zx } from '@traversable/zod'

    const MySchema = z.object({
    a: z.number(),
    b: z.object({
    c: z.string()
    })
    })

    console.log(zx.deepLoose.writeable(MySchema))
    // =>
    // z.looseObject({
    // a: z.number(),
    // b: z.looseObject({
    // c: z.string()
    // })
    // })
    import { z } from 'zod'
    import { zx } from '@traversable/zod'

    const MySchema = zx.deepNonLoose(
    z.looseObject({
    a: z.number(),
    b: z.looseObject({
    c: z.string()
    })
    })
    )
    import { z } from 'zod'
    import { zx } from '@traversable/zod'

    const MySchema = z.looseObject({
    a: z.number(),
    b: z.looseObject({
    c: z.string()
    })
    })

    console.log(zx.deepNonLoose.writeable(MySchema))
    // =>
    // z.object({
    // a: z.number(),
    // b: z.object({
    // c: z.string()
    // })
    // })
    import { z } from 'zod'
    import { zx } from '@traversable/zod'

    const MySchema = zx.deepRequired(z.object({ a: z.number().optional(), b: z.object({ c: z.string().optional() }) }))

    type MySchema = z.infer<typeof MySchema>
    // ^? type MySchema = { a: number, b: { c: string } }
    import { z } from 'zod'
    import { zx } from '@traversable/zod'

    const MySchema = z.object({
    a: z.number().optional(),
    b: z.optional(
    z.object({
    c: z.string(),
    d: z.array(z.boolean()).optional()
    })
    )
    })

    console.log(zx.deepRequired.writeable(MySchema))
    // =>
    // z.object({
    // a: z.number(),
    // b: z.object({
    // c: z.string(),
    // d: z.array(z.boolean())
    // })
    // })
    import { z } from 'zod'
    import { zx } from '@traversable/zod'

    const MySchema = zx.deepNullable(z.object({ a: z.number(), b: z.object({ c: z.string() }) }))

    type MySchema = z.infer<typeof MySchema>
    // ^? type MySchema = { a: number | null, b: { c: string | null } | null }
    import { z } from 'zod'
    import { zx } from '@traversable/zod'

    const MySchema = z.object({
    a: z.number(),
    b: z.object({
    c: z.string(),
    d: z.array(z.boolean())
    })
    })

    console.log(zx.deepNullable.writeable(MySchema))
    // =>
    // z.object({
    // a: z.number().nullable(),
    // b: z.object({
    // c: z.string().nullable(),
    // d: z.array(z.boolean()).nullable()
    // }).nullable()
    // })
    import { z } from 'zod'
    import { zx } from '@traversable/zod'

    const MySchema = zx.deepNonNullable(
    z.object({
    a: z.number().nullable(),
    b: z.object({
    c: z.string().nullable(),
    }),
    })
    )

    type MySchema = z.infer<typeof MySchema>
    // ^? type MySchema = { a: number, b: { c: string } }
    import { z } from 'zod'
    import { zx } from '@traversable/zod'

    const MySchema = z.object({
    a: z.number().nullable(),
    b: z.object({
    c: z.string().nullable(),
    })
    })

    console.log(zx.deepNonNullable.writeable(MySchema))
    // =>
    // z.object({
    // a: z.number(),
    // b: z.object({
    // c: z.string(),
    // })
    // })
    import { z } from 'zod'
    import { zx } from '@traversable/zod'

    const MySchema = zx.deepReadonly(z.object({ a: z.number(), b: z.object({ c: z.string() }) }))

    type MySchema = z.infer<typeof MySchema>
    // ^? type MySchema = { readonly a: number, readonly b: { readonly c: string } }
    import { z } from 'zod'
    import { zx } from '@traversable/zod'

    const MySchema = z.object({ a: z.number(), b: z.object({ c: z.string() }) })

    console.log(zx.deepReadonly.writeable(MySchema))
    // =>
    // z.object({
    // a: z.number().readonly(),
    // b: z.object({
    // c: z.string().readonly()
    // }).readonly()
    // }).readonly()
    import { z } from 'zod'
    import { zx } from '@traversable/zod'

    const MySchema = zx.deepStrict(
    z.object({
    a: z.number(),
    b: z.object({
    c: z.string()
    })
    })
    )
    import { z } from 'zod'
    import { zx } from '@traversable/zod'

    const MySchema = z.object({
    a: z.number(),
    b: z.object({
    c: z.string()
    })
    })

    console.log(zx.deepStrict.writeable(MySchema))
    // =>
    // z.strictObject({
    // a: z.number(),
    // b: z.strictObject({
    // c: z.string()
    // })
    // })
    import { z } from 'zod'
    import { zx } from '@traversable/zod'

    const MySchema = zx.deepNonStrict(
    z.strictObject({
    a: z.number(),
    b: z.strictObject({
    c: z.string()
    })
    })
    )
    import { z } from 'zod'
    import { zx } from '@traversable/zod'

    const MySchema = z.strictObject({
    a: z.number(),
    b: z.strictObject({
    c: z.string()
    })
    })

    console.log(zx.deepNonStrict.writeable(MySchema))
    // =>
    // z.object({
    // a: z.number(),
    // b: z.object({
    // c: z.string()
    // })
    // })

    zx.typeof returns the "type" (or tag) of a Zod schema.

    import { z } from 'zod'
    import { zx } from '@traversable/zod'

    console.log(zx.typeof(z.string())) // => "string"

    zx.tagged lets you construct a type-guard that identifies the type of Zod schema you have.

    import { z } from 'zod'
    import { zx } from '@traversable/zod'

    zx.tagged('object', z.object({})) // true
    zx.tagged('array', z.string()) // false
    Note

    zx.makeLens still experimental (🔬). Use in production with care.

    zx.makeLens accepts a zod schema (classic, v4) as its first argument, and a "selector function" as its second argument.

    An optic is a generalization of a lens, but since most people use "lens" to refer to optics generally, they are sometimes used interchangeably in this document.

    With zx.makeLens, you use a selector function to build up an optic via a series of property accesses.

    Let's look at a few examples to make things more concrete.

    For our first example, let's create a lens that focuses on a structure's "a[0]" path:

    import { z } from 'zod'
    import { zx } from '@traversable/zod'

    //////////////////////////
    /// example #1: Lens ///
    //////////////////////////

    const Schema = z.object({ a: z.tuple([z.string(), z.bigint()]) })

    // Use autocompletion to "select" what you want to focus:
    // ↆↆↆↆↆↆ
    const Lens = zx.makeLens(Schema, $ => $.a[0])

    Lens
    // ^? const Lens: zx.Lens<{ a: [string, bigint] }, string>
    // 𐙘___________________𐙘 𐙘____𐙘
    // structure focus

    // Lenses have 3 properties:

    ///////////////
    // #1:
    // Lens.get -- Given a structure,
    // returns the focus

    const ex_01 = Lens.get({ a: ['hi', 0n] })
    // 𐙘_____________𐙘
    // structure

    console.log(ex_01) // => "hi"
    // 𐙘𐙘
    // focus


    ///////////////
    // #2:
    // Lens.set -- Given a new focus and a structure,
    // sets the new focus & returns the structure

    const ex_02 = Lens.set(`hey, ho, let's go`, { a: ['', 0n] })
    // 𐙘_______________𐙘 𐙘___________𐙘
    // new focus structure

    console.log(ex_02) // => { a: ["hey, ho, let's go", 0n] }
    // 𐙘_______________𐙘
    // new focus


    /////////////////
    // #3:
    // Lens.modify -- Given a "modify" callback and a structure,
    // applies the callback to the focus & returns the structure

    const ex_03 = Lens.modify((str) => str.toUpperCase(), { a: [`hey, ho`, 0n] })
    // 𐙘_______________________𐙘 𐙘__________________𐙘
    // callback structure

    console.log(ex_03) // => { a: ["HEY, HO", 0n] }
    // 𐙘_____𐙘
    // new focus

    // Note that if your callback changes the focus type,
    // that will be reflected in the return type as well:

    const ex_04 = Lens.modify((str) => str.length > 0, { a: ['', 0n] })
    // 𐙘____________________𐙘 𐙘___________𐙘
    // callback structure

    console.log(ex_04) // => { a: [false, 0n] }
    // ^? const ex_04: { a: [boolean, bigint] }
    // 𐙘_____𐙘
    // new focus

    When you use zx.makeLens on a union type, you get back a different kind of lens called a prism.

    Let's see how prisms differ from lenses:

    import { z } from 'zod'
    import { zx } from '@traversable/zod'

    ///////////////////////////
    /// example #2: Prism ///
    ///////////////////////////

    const Schema = z.union([
    z.object({ tag: z.literal('ONE'), ghi: z.number() }),
    z.object({ tag: z.literal('TWO') })
    ])

    // Let's focus on the first union member's "ghi" property.

    // If a discriminant can be inferred, autocompletion allows
    // you to select that member by its discriminant,
    // prefixed by `ꖛ`:
    //
    // ↆↆↆↆↆ
    const Prism = zx.makeLens(Schema, $ => $.ꖛONE.ghi)

    Prism
    // ^? Prism: zx.Prism<{ tag: "ONE", ghi: number } | { tag: "TWO" }, number | undefined>
    // 𐙘________________________________________𐙘 𐙘________________𐙘
    // structure focus

    // Prisms have the same 3 properties as lenses,
    // but they behave like **pattern matchers**
    // instead of _property accessors_

    ///////////////
    // #1:
    // Prism.get -- Given a matching structure,
    // returns the focus

    const ex_01 = Prism.get({ tag: 'ONE', ghi: 123 })
    // 𐙘____________________𐙘
    // structure

    console.log(ex_01) // => 123
    // 𐙘𐙘𐙘
    // focus

    // Prism.get -- If the match fails,
    // returns undefined

    const ex_02 = Prism.get({ tag: 'TWO' })
    // 𐙘___________𐙘
    // structure

    console.log(ex_02) // => undefined
    // 𐙘𐙘𐙘
    // no match


    ///////////////
    // #2:
    // Prism.set -- Given a new focus and a matching structure,
    // sets the new focus & returns the structure

    const ex_03 = Prism.set(9_000, { tag: 'ONE', ghi: 123 })
    // 𐙘___𐙘 𐙘____________________𐙘
    // new focus structure

    console.log(ex_03) // => { tag: 'ONE', ghi: 9000 }
    // 𐙘__𐙘
    // new focus

    // Prism.set -- If the match fails,
    // returns the structure unchanged

    const ex_04 = Prism.set(9000, { tag: 'TWO' })

    console.log(ex_04) // => { tag: 'TWO' }
    // 𐙘__________𐙘
    // no match


    //////////////////
    // #3:
    // Prism.modify -- Given a "modify" callback and a matching structure,
    // applies the callback to the focus & returns the structure

    // Just like with lenses, if your callback changes the focus type,
    // that will be reflected in the return type:

    const ex_05 = Prism.modify((n) => [n, n], { tag: 'ONE', ghi: 123 })
    // 𐙘___________𐙘 𐙘____________________𐙘
    // callback structure

    console.log(ex_05) // => { tag: 'ONE', ghi: [123, 123] }
    // ^? const ex_05: { tag: "ONE", ghi: number[] } | { tag: "TWO" }

    // Prism.modify -- If the match fails,
    // returns the structure unchanged

    const ex_06 = Prism.modify((n) => n + 1, { tag: 'TWO' })
    // 𐙘__________𐙘 𐙘___________𐙘
    // callback structure

    console.log(ex_06) // => { tag: 'TWO' }
    // ^? const ex_06: { tag: "ONE", ghi: number } | { tag: "TWO" }

    When you use zx.makeLens on a collection type (such as z.array or z.record), you get back a different kind of lens called a traversal.

    Let's see how traversals differ from lenses and prisms:

    import { z } from 'zod'
    import { zx } from '@traversable/zod'

    ///////////////////////////////
    /// example #3: Traversal ///
    ///////////////////////////////

    const Schema = z.object({
    a: z.array(
    z.object({
    b: z.number(),
    c: z.string()
    })
    )
    })

    // Let's focus on the `"b"` property of each of the elements of the structure's `"a"` property:

    // To indicate that you want to traverse the array,
    // autocomplete the `ᣔꓸꓸ` field:
    // ↆↆ
    const Traversal = zx.makeLens(Schema, $ => $ => $.a.ᣔꓸꓸ.b)


    Traversal
    // ^? Traversal: zx.Traversal<{ a: { b: number, c: string }[] }, number>
    // 𐙘_____________________________𐙘 𐙘____𐙘
    // structure focus

    // Traversals have the same 3 properties as lenses and prisms,
    // but they behave like **for-of loops**
    // instead of _property accessors_ or _patterns matchers_


    ///////////////
    // #1:
    // Traversal.get -- Given a matching structure,
    // returns all of the focuses

    const ex_01 = Traversal.get({ a: [{ b: 0, c: '' }, { b: 1, c: '' }] })
    // 𐙘_____________________________________𐙘
    // structure

    console.log(ex_01) // => [0, 1]
    // 𐙘__𐙘
    // focus


    ///////////////
    // #2:
    // Traversal.set -- Given a new focus and a matching structure, sets all of the elements
    // of the collection to the new focus & returns the structure

    const ex_02 = Traversal.set(9_000, { a: [{ b: 0, c: '' }, { b: 1, c: '' }] })
    // 𐙘___𐙘 𐙘_____________________________________𐙘
    // new focus structure

    console.log(ex_02) // => { a: [{ b: 9000, c: '' }, { b: 9000, c: '' }] }
    // 𐙘__𐙘 𐙘__𐙘
    // new focus new focus


    //////////////////
    // #3:
    // Traversal.modify -- Given a "modify" callback and a matching structure,
    // applies the callback to _each_ focus & returns the structure

    // Just like with lenses & prisms, if your callback changes the focus type,
    // that will be reflected in the return type:

    const ex_03 = Traversal.modify((n) => [n, n + 1], { a: [{ b: 0, c: '' }, { b: 1, c: '' }] })
    // 𐙘______________𐙘 𐙘_____________________________________𐙘
    // callback structure

    console.log(ex_03) // => { a: [{ b: [0, 1], c: '' }, { b: [1, 2], c: '' }] }
    // ^? const ex_03: { a: { b: number[], c: string }[] }
    // 𐙘______𐙘
    // new focus
    Note

    zx.fold is an advanced API.

    Use zx.fold to define a recursive traversal of a Zod schema. Useful when building a schema rewriter.

    zx.fold is a powertool. Most of @traversable/zod uses zx.fold under the hood.

    Compared to the rest of the library, it's fairly "low-level", so unless you're doing something more advanced you probably won't need to use it directly.

    1. Example: Custom schema rewriter

    Let's write a schema rewriter that takes an arbitrary Zod schema, and applies a custom transformation to only z.string schemas. For this contrived example, we'll be converting string values to uppercase.

    Note

    You can play with this example on StackBlitz

    import * as z from 'zod'
    import { zx } from '@traversable/zod'

    function rewriter<T extends z.ZodType>(type: T): T
    function rewriter<T>(type: z.ZodType<T>) {
    return fold<z.ZodType>((x) => {
    switch (true) {
    case zx.tagged('string')(x): return x.transform((v) => v.toUpperCase())
    default: return z.clone(x as z.ZodType, x._zod.def as z.core.$ZodTypeDef)
    }
    })(type)
    }

    const Ex01 = rewriter(z.uuid())
    // ^? const Ex01: z.ZodUUID

    console.log(Ex01.parse('fdbe3218-bba3-4cf9-95d6-0a0a3770fb64'))
    // => "FDBE3218-BBA3-4CF9-95D6-0A0A3770FB64"

    const Ex02 = rewriter(z.object({ id: z.uuid() }))
    // ^? const Ex02: z.ZodObject<{ id: z.ZodUUID }>

    console.log(Ex02.parse({ id: '012f33de-023b-414e-a0a8-0ff9e1e53545' }))
    // => { "id": "012F33DE-023B-414E-A0A8-0FF9E1E53545" }
    Note

    Notice the use of z.clone: this is only necessary when your target is also a Zod schema. This is to ensure that none of the schema's class properties are lost in the traversal.

    Thanks to @Refzlund for suggesting that we add this example to the docs!

    1. Example: Mock data generator

    Let's write a function that takes an arbitrary Zod schema, and generates mock data that satisfies the schema (a.k.a. a "faker").

    Note

    You can play with this example on StackBlitz

    import { z } from 'zod/v4'
    import { F, tagged } from '@traversable/zod-types'
    import { faker } from '@faker-js/faker'

    type Fake = () => unknown

    const fake = F.fold<Fake>((x) => {
    // 𐙘__𐙘 this type parameter fills in the "holes" below
    switch (true) {
    case tagged('array')(x): return () => faker.helpers.multiple(
    () => x._zod.def.element()
    // ^? method element: Fake
    // 𐙘__𐙘
    )
    case tagged('never')(x): return () => void 0
    case tagged('unknown')(x): return () => void 0
    case tagged('any')(x): return () => void 0
    case tagged('void')(x): return () => void 0
    case tagged('null')(x): return () => null
    case tagged('undefined')(x): return () => undefined
    case tagged('nan')(x): return () => NaN
    case tagged('boolean')(x): return () => faker.datatype.boolean()
    case tagged('symbol')(x): return () => Symbol()
    case tagged('int')(x): return () => faker.number.int()
    case tagged('bigint')(x): return () => faker.number.bigInt()
    case tagged('number')(x): return () => faker.number.float()
    case tagged('string')(x): return () => faker.lorem.words()
    case tagged('date')(x): return () => faker.date.recent()
    case tagged('literal')(x): return () => faker.helpers.arrayElement(x._zod.def.values)
    case tagged('template_literal')(x): return () => faker.helpers.fromRegExp(x._zod.pattern)
    case tagged('enum')(x): return () => faker.helpers.arrayElement(Array.from(x._zod.values))
    case tagged('nonoptional')(x): return x._zod.def.innerType
    case tagged('nullable')(x): return x._zod.def.innerType
    case tagged('optional')(x): return x._zod.def.innerType
    case tagged('readonly')(x): return x._zod.def.innerType
    case tagged('catch')(x): return x._zod.def.innerType
    case tagged('default')(x): return x._zod.def.innerType
    case tagged('prefault')(x): return x._zod.def.innerType
    case tagged('success')(x): return x._zod.def.innerType
    case tagged('pipe')(x): return x._zod.def.out
    case tagged('lazy')(x): return x._zod.def.getter()
    case tagged('promise')(x): return x._zod.def.innerType
    case tagged('set')(x): return () => new Set([x._zod.def.valueType()])
    case tagged('map')(x): return () => new Map([[x._zod.def.keyType(), x._zod.def.valueType()]])
    case tagged('intersection')(x): return () => Object.assign({}, x._zod.def.left(), x._zod.def.right())
    case tagged('union')(x): return () => faker.helpers.arrayElement(x._zod.def.options.map((option) => option()))
    case tagged('tuple')(x): return () => x._zod.def.items.map((item) => item())
    case tagged('record')(x): return () => Object.fromEntries([[x._zod.def.keyType(), x._zod.def.valueType()]])
    case tagged('object')(x): return () => Object.fromEntries(Object.entries(x._zod.def.shape).map(([k, v]) => [k, v()]))
    case tagged('file')(x): return () => new File(faker.lorem.lines(10).split('\n'), faker.system.commonFileName())
    case tagged('custom')(x): { throw Error('Unsupported schema: z.custom') }
    case tagged('transform')(x): { throw Error('Unsupported schema: z.transform') }
    default: { x satisfies never; throw Error('Illegal state') }
    // 𐙘_______________𐙘
    // exhaustiveness check works
    }
    })

    // Let's test it out:
    const mock = fake(
    z.object({
    abc: z.array(z.string()),
    def: z.optional(
    z.tuple([
    z.number(),
    z.boolean()
    ])
    )
    })
    )

    console.log(mock())
    // => {
    // abc: [
    // 'annus iure consequatur',
    // 'aer suus autem',
    // 'delectus patrocinor deporto',
    // 'benevolentia tonsor odit',
    // 'stabilis dolor tres',
    // 'mollitia quibusdam vociferor'
    // ],
    // def: [-882, false]
    // }
    Note

    zx.Functor is an advanced API

    zx.Functor is the primary abstraction that powers @traversable/zod.

    zx.Functor is a powertool. Most of @traversable/zod uses zx.Functor under the hood.

    Compared to the rest of the library, it's fairly "low-level", so unless you're doing something pretty advanced you probably won't need to use it directly.

    Namespaces

    check
    deepClone
    deepEqual
    deepNoDefaults
    deepNonNullable
    deepNullable
    deepOptional
    deepPartial
    deepReadonly
    deepRequired
    F
    toPaths
    toString
    toType
    Z
    zx

    Interfaces

    CompilerIndex
    Config
    Ctx
    ZodArray
    ZodObject
    ZodRecord
    ZodTuple
    ZodType

    Type Aliases

    Algebra
    Any
    AnyTypeName
    Atoms
    CompilerAlgebra
    ConvertCase
    DeepClonePrimitive
    deepNonNullable
    deepNullable
    deepOptional
    deepPartial
    deepReadonly
    deepRequired
    defaultValue
    Discriminated
    Fold
    fromJson
    HasTypeName
    Index
    NullaryTypeName
    Options
    PathSpec
    Primitive
    RAISE_ISSUE_URL
    Sym
    Tagged
    TypeName
    UnaryTypeName
    VERSION
    ZOD_CHANGELOG

    Variables

    compile
    CompilerFunctor
    Ctx
    deepCamelCaseCodec
    deepSnakeCaseCodec
    defaultIndex
    defaultNextSpec
    defaultPrevSpec
    defaults
    fold
    functorDefaultOptions
    hasTypeName
    invalidValue
    Invariant
    isNullary
    isNullaryTypeName
    isOptional
    isUnary
    map
    NS
    nullaryTypeNames
    PromiseSchemaIsUnsupported
    RAISE_ISSUE_URL
    removePrototypeMethods
    Sym
    tagged
    TypeName
    TypeNames
    typeof
    VERSION
    Warn
    ZOD_CHANGELOG
    ZOD_VERSION

    Functions

    areAllObjects
    camelCase
    check
    convertCaseCodec
    deepClone
    deepCloneInlinePrimitiveCheck
    deepCloneIsPrimitive
    deepCloneSchemaOrdering
    deepEqual
    deepLoose
    deepNoDefaults
    deepNonLoose
    deepNonNullable
    deepNonStrict
    deepNullable
    deepOptional
    deepPartial
    deepReadonly
    deepRequired
    deepStrict
    defaultValue
    fromConstant
    fromJson
    getFallback
    getSubSchema
    getTags
    hasDefault
    hasOptional
    in
    inlinePrimitiveCheck
    isNullish
    isNumeric
    isPrimitive
    isScalar
    isSpecialCase
    isTypelevelNullary
    lift
    makeLens
    out
    parsePath
    schemaOrdering
    serializeShort
    snakeCase
    toPaths
    toString
    toType

    References

    Functor → F.Functor