Skip to main content
Version: v2

ZodSchemer

note

ZodSchemer requires the zod dependency (v3) to be installed as peer dependency:

npm install zod@v3

Transpiles DynamoDB-Toolbox schemas to Zod Schemas. Note that the transpilation itself is type-safe, which means that resulting schema types can be introspected and type inference is preserved:

import { ZodSchemer } from 'dynamodb-toolbox/schema/actions/zodSchemer'

const zodSchemer = pokemonSchema.build(ZodSchemer)

const zodParser = zodSchemer.parser()
const parsed = zodParser.parse(input)
// => TransformedValue<typeof pokemonSchema>

const zodFormatter = zodSchemer.formatter()
const formatted = zodFormatter.parse(parsed)
// => FormattedValue<typeof pokemonSchema>
note

Because DynamoDB-Toolbox schema are more flexible than Zod Schemas (Parsing vs Formatting, Item vs Keys, Updates syntax etc.), you may need to transpile several Zod Schemas for different contexts.

You can also derive schemas from Zod schemas with the [fromZodSchema] util:

import { z } from 'zod'
import { fromZodSchema } from 'dynamodb-toolbox/schema/actions/fromZodSchema'

const zodSchema = z.object({
firstName: z.string(),
lastName: z.string()
})
const ddbToolboxSchema = fromZodSchema(zodSchema)

Again the transpilation itself is type-safe and type inference is preserved, although the resulting schema may have to be tweaked to introduce DynamoDB related features (keys, links, transforms etc.).

⚠️ Known Limitations​

ZodSchemer​

The most important limitation at the moment is that links are not transpiled (but defaults are):

const nameSchema = item({
firstName: string(),
lastName: string()
})

const fullSchema = nameSchema.and({
fullName: string().link<typeof nameSchema>(
({ firstName, lastName }) =>
[firstName, lastName].join(' ')
)
}))

const zodSchema = fullSchema.build(ZodSchemer).parser()
// ❌ Error: Missing `fullName` attribute
zodSchema.parse({ firstName: 'John', lastName: 'Dow' })

Also, hidden attributes are simply stripped for now (thus not validated during formatting):

const schema = map({
hidden: string().hidden()
})

const zodSchema = schema.build(ZodSchemer).formatter()
// ⚠️ Does not throw even if `hidden` is required
zodSchema.parse({})

Due to some discrepancies between schema systems, discriminated unions also suffer from some minor limitations:

  • Discriminated AND remapped maps cannot be transpiled:
const schema = anyOf(
map({ type: string().const('a').savedAs('_t') }),
map({ type: string().const('b').savedAs('_t') })
).discriminate('type')

const zodSchema = schema.build(ZodSchemer).parser()
// ❌ Fails: `ZodDiscriminatedUnion` does not support `ZodEffect` options
zodSchema.parse(input)
  • Discriminated AND refined maps cannot be transpiled (should be fixed with Zod v4):
const schema = anyOf(
map({ type: string().const('a') }).validate(...),
map({ type: string().const('b') }).validate(...)
).discriminate('type')

const zodSchema = schema.build(ZodSchemer).parser()
// ❌ Fails: `ZodDiscriminatedUnion` does not support `ZodEffect` options
zodSchema.parse(input)
  • Nested discriminated union cannot be transpiled (should be fixed with Zod v4):
const schema = anyOf(
map({ type: string().const('a') }),
anyOf(
map({ type: string().const('b') }),
map({ type: string().const('c') })
)
).discriminate('type')

const zodSchema = schema.build(ZodSchemer).parser()
// ❌ Fails: `ZodDiscriminatedUnion` does not support `ZodDiscriminatedUnion` options
zodSchema.parse(input)

fromZodSchema​

Binary schemas (Uint8Array) do not exist in zod and are cannot be transpiled (see this issue).

Refinments are transpiled but transforms are not (should be fixed with Zod v4 codecs):

const zodSchema = z
.string()
.validate(value => value.length > 0)

const schema = fromZodSchema(zodSchema)
schema.build(Parser).parse('') // ❌ Fails

const zodSchema = z.transform(value => value.trim())

const schema = fromZodSchema(zodSchema)
schema.build(Parser).parse(' foo ') // => ' foo '

Preprocessed schemas and intersections are not supported, as well as types that are not natively available in DynamoDB are (Dates, NaNs, InstanceOf etc.).

Methods​

parser(...)​

(options?: ZodParserOptions) => ZodParser<SCHEMA>

Creates a zodSchema to use for Item parsing. It includes defaults, custom validation and transformations (see the Parser action for more details on parsing):

import { z } from 'zod'

const nameSchema = item({
firstName: string().savedAs('_f'),
lastName: string().validate(input => input.length > 1)
})

const zodParser = nameSchema.build(ZodSchemer).parser()

// ❌ `firstName` missing
zodParser.parse({ lastName: 'Dow' })
// ❌ `lastName` too short
zodParser.parse({ firstName: 'John', lastName: '' })
// βœ… Success
zodParser.parse({ firstName: 'John', lastName: 'Dow' })

type ParsedName = z.infer<typeof zodParser>
// => { _f: string, lastName: string } πŸ™Œ

You can provide additional options. Available options:

OptionTypeDefaultDescription
fillbooleantrueWhether to include defaults in the resulting schema or not (links are NOT transpiled at the moment).
transformbooleantrueWhether to include post-validation transformations (i.e. savedAs and transform) in the resulting schema or not.
modeput or keyputWether to keep only key attributes in the schema or not (update mode is NOT supported at the moment).
definedbooleanfalseWhether to reject undefined values in the resulting schema or not (even if the original schema is optional).
Examples
const schema = item({
pokemonId: string(),
level: number().default(1)
})

const zodSchemer = schema.build(ZodSchemer)
const zodSchema = zodSchemer.parser({ fill: false })

// ❌ `level` missing
zodSchema.parse({ pokemonId: 'pikachu1' })

formatter(...)​

(options?: ZodFormatterOptions) => ZodFormatter<SCHEMA>

Creates a zodSchema to use for Item formatting. It includes transformations, custom validation and formatting (see the Formatter action for more details on formatting):

import { z } from 'zod'

const nameSchema = item({
firstName: string().savedAs('_f'),
lastName: string().validate(input => input.length > 1)
})

const zodFormatter = nameSchema
.build(ZodSchemer)
.formatter()

// ❌ `_f` missing
zodFormatter.parse({ lastName: 'Dow' })
// ❌ `lastName` too short
zodFormatter.parse({ _f: 'John', lastName: '' })
// βœ… Success
zodFormatter.parse({ _f: 'John', lastName: 'Dow' })

type FormattedName = z.infer<typeof zodFormatter>
// => { firstName: string, lastName: string } πŸ™Œ

You can provide additional options. Available options:

OptionTypeDefaultDescription
transformbooleantrueWhether to include pre-validation transformations (i.e. savedAs and transform) in the resulting schema or not.
formatbooleantrueWhether to strip hidden attributes from the resulting schema or not.
partialbooleanputWether to make every attribute (flat or deep) optional in the schema or not.
definedbooleanfalseWhether to reject undefined values in the resulting schema or not (even if the original schema is optional).
Examples
const schema = item({
pokemonId: string().savedAs('id'),
level: number().transform(input => input + 1)
})

const zodSchemer = schema.build(ZodSchemer)
const zodSchema = zodSchemer.formatter({ transform: false })

zodSchema.parse({ pokemonId: 'pikachu1', level: 1 })
// βœ… => { pokemonId: 'pikachu1', level: 1 }