Skip to main content
Version: v2

ZodSchemer

note

ZodSchemer requires the zod dependency to be installed first:

npm install zod

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.format(parsed)
// => FormattedValue<typeof pokemonSchema>
note

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

⚠️ Known Limitations​

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.format({})

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)

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
zodParser.parse({ lastName: 'Dow' })
// ❌ `lastName` too short
zodParser.parse({ _f: 'John', lastName: '' })
// βœ… Success
zodParser.parse({ _f: 'John', lastName: 'Dow' })

type ParsedName = z.infer<typeof zodParser>
// => { 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 }