Last active
October 1, 2025 10:46
-
-
Save logickoder/4e4cd45bddc54fd16ce88a600485335e to your computer and use it in GitHub Desktop.
Zod Helper
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { | |
| z, | |
| ZodArray, | |
| ZodBoolean, | |
| ZodDefault, | |
| ZodEffects, | |
| ZodEnum, | |
| ZodLiteral, | |
| ZodNumber, | |
| ZodObject, | |
| ZodOptional, | |
| ZodRawShape, | |
| ZodRecord, | |
| ZodString, | |
| ZodUnion, | |
| } from 'zod'; | |
| // Helper type to extract initial values type from a Zod schema | |
| type InitialValues<T extends ZodRawShape> = { | |
| [K in keyof T]: T[K] extends ZodDefault<any> | |
| ? ReturnType<T[K]['_def']['defaultValue']> | |
| : T[K] extends ZodOptional<any> | |
| ? undefined | |
| : T[K] extends ZodBoolean | |
| ? boolean | |
| : T[K] extends ZodString | |
| ? string | |
| : T[K] extends ZodNumber | |
| ? number | |
| : T[K] extends ZodArray<any> | |
| ? Array<z.infer<T[K]['element']>> | |
| : T[K] extends ZodObject<any> | |
| ? InitialValues<T[K]['shape']> | |
| : T[K] extends ZodRecord<any> | |
| ? Record<string, any> | |
| : T[K] extends ZodEnum<any> | |
| ? T[K]['_def']['values'][number] | |
| : T[K] extends ZodUnion<any> | |
| ? z.infer<T[K]> | |
| : T[K] extends ZodLiteral<any> | |
| ? T[K]['_def']['value'] | |
| : null; | |
| }; | |
| function unwrapEffects(schema: any): any { | |
| while (schema instanceof ZodEffects) { | |
| schema = schema._def.schema; | |
| } | |
| return schema; | |
| } | |
| export const extractInitialValues = <T extends ZodRawShape>( | |
| schema: ZodObject<T> | ZodEffects<ZodObject<T>> | |
| ): InitialValues<T> => { | |
| const unwrapped = unwrapEffects(schema) as ZodObject<T>; | |
| const shape = unwrapped.shape; | |
| const initialValues: Record<string, any> = {}; | |
| for (const [key, value] of Object.entries(shape)) { | |
| const field = unwrapEffects(value); | |
| if (field instanceof ZodDefault) { | |
| initialValues[key] = field._def.defaultValue(); | |
| } else if (field instanceof ZodOptional) { | |
| initialValues[key] = undefined; | |
| } else if (field instanceof ZodBoolean) { | |
| initialValues[key] = false; | |
| } else if (field instanceof ZodString) { | |
| initialValues[key] = ''; | |
| } else if (field instanceof ZodNumber) { | |
| initialValues[key] = 0; | |
| } else if (field instanceof ZodArray) { | |
| initialValues[key] = []; | |
| } else if (field instanceof ZodObject) { | |
| initialValues[key] = extractInitialValues(field); | |
| } else if (field instanceof ZodRecord) { | |
| initialValues[key] = {}; | |
| } else if (field instanceof ZodEnum) { | |
| initialValues[key] = field._def.values[0]; // Pick first enum value as default | |
| } else if (field instanceof ZodLiteral) { | |
| initialValues[key] = field._def.value; | |
| } else if (field instanceof ZodUnion) { | |
| initialValues[key] = null; // Unions are tricky; default to null | |
| } else { | |
| initialValues[key] = null; | |
| } | |
| } | |
| return initialValues as InitialValues<T>; | |
| }; | |
| export function createZodField( | |
| fieldName: string, | |
| props?: { | |
| min?: number; | |
| max?: number; | |
| customMessage?: string; | |
| type?: 'string' | 'number'; | |
| }, | |
| ) { | |
| const displayName = fieldName | |
| .replace(/_/g, ' ') | |
| .replace(/\b\w/g, l => l.toUpperCase()); | |
| const requiredMsg = props?.customMessage || `${displayName} is required`; | |
| const min = props?.min ?? 1; | |
| const type = props?.type ?? 'string'; | |
| const params = { | |
| required_error: requiredMsg, | |
| invalid_type_error: requiredMsg, | |
| }; | |
| let result = (type === 'string' ? z.string(params) : z.number(params)).min( | |
| min, | |
| min > 1 | |
| ? `${displayName} must be at least ${min}${type === 'string' ? ' characters' : '' | |
| }` | |
| : requiredMsg, | |
| ); | |
| if (props?.max) { | |
| result = result.max( | |
| props.max, | |
| `${displayName} must be at most ${min}${type === 'string' ? ' characters' : '' | |
| }`, | |
| ); | |
| } | |
| return result; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { | |
| z, | |
| ZodArray, | |
| ZodBoolean, | |
| ZodDefault, | |
| ZodEnum, | |
| ZodLiteral, | |
| ZodNumber, | |
| ZodObject, | |
| ZodOptional, | |
| ZodRawShape, | |
| ZodRecord, | |
| ZodString, | |
| ZodUnion, | |
| } from 'zod'; | |
| // Helper type to extract initial values type from a Zod schema | |
| type InitialValues<T extends ZodRawShape> = { | |
| [K in keyof T]: T[K] extends ZodDefault<any> | |
| ? ReturnType<T[K]['_def']['defaultValue']> | |
| : T[K] extends ZodOptional<any> | |
| ? undefined | |
| : T[K] extends ZodBoolean | |
| ? boolean | |
| : T[K] extends ZodString | |
| ? string | |
| : T[K] extends ZodNumber | |
| ? number | |
| : T[K] extends ZodArray<any> | |
| ? Array<z.infer<T[K]['element']>> | |
| : T[K] extends ZodObject<any> | |
| ? InitialValues<T[K]['shape']> | |
| : T[K] extends ZodRecord<any> | |
| ? Record<string, any> | |
| : T[K] extends ZodEnum<any> | |
| ? T[K]['def']['entries'][number] | |
| : T[K] extends ZodUnion<any> | |
| ? z.infer<T[K]> | |
| : T[K] extends ZodLiteral<any> | |
| ? T[K]['def']['values'][number] | |
| : null; | |
| }; | |
| export const extractInitialValues = <T extends ZodRawShape>( | |
| schema: ZodObject<T>, | |
| ): InitialValues<T> => { | |
| const shape = schema.shape; | |
| const initialValues: Record<string, any> = {}; | |
| for (const [key, value] of Object.entries(shape)) { | |
| const field = value; | |
| if (field instanceof ZodDefault) { | |
| // @ts-ignore | |
| initialValues[key] = field.def.defaultValue(); | |
| } else if (field instanceof ZodOptional) { | |
| initialValues[key] = undefined; | |
| } else if (field instanceof ZodBoolean) { | |
| initialValues[key] = false; | |
| } else if (field instanceof ZodString) { | |
| initialValues[key] = ''; | |
| } else if (field instanceof ZodNumber) { | |
| initialValues[key] = 0; | |
| } else if (field instanceof ZodArray) { | |
| initialValues[key] = []; | |
| } else if (field instanceof ZodObject) { | |
| initialValues[key] = extractInitialValues(field); | |
| } else if (field instanceof ZodRecord) { | |
| initialValues[key] = {}; | |
| } else if (field instanceof ZodEnum) { | |
| initialValues[key] = field.def.entries[0]; | |
| } else if (field instanceof ZodLiteral) { | |
| initialValues[key] = field.def.values[0]; | |
| } else if (field instanceof ZodUnion) { | |
| initialValues[key] = null; // Unions are tricky; default to null | |
| } else { | |
| initialValues[key] = null; | |
| } | |
| } | |
| return initialValues as InitialValues<T>; | |
| }; | |
| export function createZodField( | |
| fieldName: string, | |
| props?: { | |
| min?: number; | |
| max?: number; | |
| customMessage?: string; | |
| type?: 'string' | 'number'; | |
| }, | |
| ) { | |
| const displayName = fieldName | |
| .replace(/_/g, ' ') | |
| .replace(/\b\w/g, l => l.toUpperCase()); | |
| const requiredMsg = props?.customMessage || `${displayName} is required`; | |
| const min = props?.min ?? 1; | |
| const type = props?.type ?? 'string'; | |
| const params = { | |
| required_error: requiredMsg, | |
| invalid_type_error: requiredMsg, | |
| }; | |
| let result = (type === 'string' ? z.string(params) : z.number(params)).min( | |
| min, | |
| min > 1 | |
| ? `${displayName} must be at least ${min}${type === 'string' ? ' characters' : '' | |
| }` | |
| : requiredMsg, | |
| ); | |
| if (props?.max) { | |
| result = result.max( | |
| props.max, | |
| `${displayName} must be at most ${min}${type === 'string' ? ' characters' : '' | |
| }`, | |
| ); | |
| } | |
| return result; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment