Skip to content

Instantly share code, notes, and snippets.

@logickoder
Last active October 1, 2025 10:46
Show Gist options
  • Select an option

  • Save logickoder/4e4cd45bddc54fd16ce88a600485335e to your computer and use it in GitHub Desktop.

Select an option

Save logickoder/4e4cd45bddc54fd16ce88a600485335e to your computer and use it in GitHub Desktop.
Zod Helper
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;
}
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