for zod3.ts works and types correctly
zod4 version yet requires some adjustments and tests
| import { z } from 'zod'; | |
| // biome-ignore lint/suspicious/noExplicitAny: generic matchers are ok | |
| type UnwrapEffects<T> = T extends z.ZodEffects<infer I, any, any> | |
| ? UnwrapEffects<I> | |
| : T; | |
| type ShapeOf<T> = UnwrapEffects<T> extends z.ZodObject<infer S> ? S : never; | |
| type Defaults<T extends z.ZodRawShape> = { | |
| // keep the key when, **after** unwrapping effects, it is: | |
| // • a ZodDefault → we take its output type | |
| // • a ZodObject → we recurse into its shape | |
| // biome-ignore lint/suspicious/noExplicitAny: generic matchers are ok | |
| [K in keyof T as UnwrapEffects<T[K]> extends z.ZodDefault<any> | |
| ? K | |
| : ShapeOf<T[K]> extends never | |
| ? never | |
| : // biome-ignore lint/suspicious/noExplicitAny: generic matchers are ok | |
| K]: UnwrapEffects<T[K]> extends z.ZodDefault<any> | |
| ? z.infer<T[K]> // keep the effects’ output type | |
| : Defaults<ShapeOf<T[K]>>; // recurse | |
| }; | |
| function unwrapEffects<T extends z.ZodTypeAny>(schema: T | z.ZodEffects<T>): T { | |
| return schema instanceof z.ZodEffects ? schema.innerType() : schema; | |
| } | |
| export function getZodDefaults<T extends z.ZodRawShape>( | |
| schema: z.ZodObject<T> | z.ZodEffects<z.ZodObject<T>>, | |
| ): Defaults<T> { | |
| const zObject = schema instanceof z.ZodEffects ? schema.innerType() : schema; | |
| return Object.fromEntries( | |
| Object.entries(zObject.shape) | |
| .map(([k, vv]) => { | |
| const v = unwrapEffects(vv); | |
| return [ | |
| k, | |
| v instanceof z.ZodDefault | |
| ? v._def.defaultValue() | |
| : v instanceof z.ZodObject | |
| ? getZodDefaults(v) | |
| : undefined, | |
| ]; | |
| }) | |
| .filter(([_k, v]) => v !== undefined), | |
| ) as Defaults<T>; | |
| } |
| /** biome-ignore lint/suspicious/noExplicitAny: match in generics it's ok */ | |
| import z from 'zod'; | |
| // Strip wrappers until we get the naked ZodObject instance | |
| const unwrapObject = (s: z.ZodTypeAny): z.ZodObject => { | |
| if (s instanceof z.ZodObject) { | |
| return s; | |
| } | |
| if ('innerType' in s && typeof s.innerType === 'function') { | |
| // ZodPipe keeps the original schema in .innerType() | |
| return unwrapObject(s.innerType()); | |
| } | |
| if ('_zod' in s && 'def' in s._zod && 'inner' in s._zod.def) { | |
| // ZodTransform keeps it in _zod.def.inner | |
| return unwrapObject(s._zod.def.inner as z.ZodTypeAny); | |
| } | |
| throw new Error('Expected an object schema or a wrapper around one'); | |
| }; | |
| // recursive Defaults<> – now aware of ZodPipe / ZodTransform | |
| export type Defaults<T extends z.ZodRawShape> = { | |
| [K in keyof T as T[K] extends z.ZodDefault<any> | |
| ? K | |
| : T[K] extends z.ZodObject<any> | |
| ? K | |
| : T[K] extends z.ZodPipe<z.ZodObject<any>, any> | |
| ? K | |
| : T[K] extends z.ZodTransform<z.ZodObject<any>, any> | |
| ? K | |
| : never]: T[K] extends z.ZodDefault<infer _U> | |
| ? z.output<T[K]> | |
| : T[K] extends z.ZodObject<infer SH> | |
| ? Defaults<SH> | |
| : T[K] extends z.ZodPipe<z.ZodObject<infer SH>, any> | |
| ? Defaults<SH> | |
| : T[K] extends z.ZodTransform<z.ZodObject<infer SH>, any> | |
| ? Defaults<SH> | |
| : never; | |
| }; | |
| export function getZodDefaults< | |
| T extends z.ZodRawShape, | |
| S extends | |
| | z.ZodObject<T> | |
| | z.ZodPipe<z.ZodObject<T>, any> | |
| | z.ZodTransform<z.ZodObject<T>, any>, | |
| >(schema: S): Defaults<T> { | |
| const obj = unwrapObject(schema) as z.ZodObject<T>; | |
| return Object.fromEntries( | |
| Object.entries(obj.shape) | |
| .map(([key, field]) => { | |
| // plain default | |
| if (field instanceof z.ZodDefault) { | |
| const def = field.def || field._zod?.def || field._def; // https://github.com/colinhacks/zod/issues/5020 | |
| return [ | |
| key, | |
| typeof def.defaultValue === 'function' | |
| ? def.defaultValue() | |
| : def.defaultValue, | |
| ]; | |
| } | |
| // nested object (possibly piped / transformed) | |
| if ( | |
| field instanceof z.ZodObject || | |
| ('innerType' in field && typeof field.innerType === 'function') || // ZodPipe is not a class? | |
| ('_zod' in field && 'def' in field._zod && 'inner' in field._zod.def) // ZodTransform is not a class? | |
| ) { | |
| return [ | |
| key, | |
| getZodDefaults( | |
| field as | |
| | z.ZodObject | |
| | z.ZodPipe<z.ZodObject<any>, any> | |
| | z.ZodTransform<z.ZodObject<any>, any>, | |
| ), | |
| ]; | |
| } | |
| // no default for this key | |
| return [key, undefined]; | |
| }) | |
| // drop keys that didn’t produce anything | |
| .filter(([, v]) => v !== undefined), | |
| ) as Defaults<T>; | |
| } |