-
-
Save TonyGravagno/2b744ceb99e415c4b53e8b35b309c29c to your computer and use it in GitHub Desktop.
| import { z } from 'zod' | |
| /** | |
| * @summary Function returns default object from Zod schema | |
| * @version 23.05.15.2 | |
| * @link https://gist.github.com/TonyGravagno/2b744ceb99e415c4b53e8b35b309c29c | |
| * @author Jacob Weisenburger, Josh Andromidas, Thomas Moiluiavon, Tony Gravagno | |
| * @param schema z.object schema definition | |
| * @param options Optional object, see Example for details | |
| * @returns Object of type schema with defaults for all fields | |
| * @example | |
| * const schema = z.object( { ... } ) | |
| * const default1 = defaultInstance<typeof schema>(schema) | |
| * const default2 = defaultInstance<typeof schema>( | |
| * schema,{ // toggle from these defaults if required | |
| * defaultArrayEmpty: false, | |
| * defaultDateEmpty: false, | |
| * defaultDateUndefined: false, | |
| * defaultDateNull: false, | |
| * } ) | |
| */ | |
| export function defaultInstance<T extends z.ZodTypeAny>( | |
| schema: z.AnyZodObject | z.ZodEffects<any>, | |
| options: object = {} | |
| ): z.infer<T> { | |
| const defaultArrayEmpty = 'defaultArrayEmpty' in options ? options.defaultArrayEmpty : false | |
| const defaultDateEmpty = 'defaultDateEmpty' in options ? options.defaultDateEmpty : false | |
| const defaultDateUndefined = 'defaultDateUndefined' in options ? options.defaultDateUndefined : false | |
| const defaultDateNull = 'defaultDateNull' in options ? options.defaultDateNull : false | |
| function run(): z.infer<T> { | |
| if (schema instanceof z.ZodEffects) { | |
| if (schema.innerType() instanceof z.ZodEffects) { | |
| return defaultInstance(schema.innerType(), options) // recursive ZodEffect | |
| } | |
| // return schema inner shape as a fresh zodObject | |
| return defaultInstance(z.ZodObject.create(schema.innerType().shape), options) | |
| } | |
| if (schema instanceof z.ZodType) { | |
| let the_shape = schema.shape as z.ZodAny // eliminates 'undefined' issue | |
| let entries = Object.entries(the_shape) | |
| let temp = entries.map(([key, value]) => { | |
| let this_default = | |
| value instanceof z.ZodEffects ? defaultInstance(value, options) : getDefaultValue(value) | |
| return [key, this_default] | |
| }) | |
| return Object.fromEntries(temp) | |
| } else { | |
| console.log(`Error: Unable to process this schema`) | |
| return null // unknown or undefined here results in complications | |
| } | |
| function getDefaultValue(dschema: z.ZodTypeAny): any { | |
| console.dir(dschema) | |
| if (dschema instanceof z.ZodDefault) { | |
| if (!('_def' in dschema)) return undefined // error | |
| if (!('defaultValue' in dschema._def)) return undefined // error | |
| return dschema._def.defaultValue() | |
| } | |
| if (dschema instanceof z.ZodArray) { | |
| if (!('_def' in dschema)) return undefined // error | |
| if (!('type' in dschema._def)) return undefined // error | |
| // return empty array or array with one empty typed element | |
| return defaultArrayEmpty ? [] : [getDefaultValue(dschema._def.type as z.ZodAny)] | |
| } | |
| if (dschema instanceof z.ZodString) return '' | |
| if (dschema instanceof z.ZodNumber || dschema instanceof z.ZodBigInt) { | |
| let value = dschema.minValue ?? 0 | |
| return value | |
| } | |
| if (dschema instanceof z.ZodDate) { | |
| let value = defaultDateEmpty | |
| ? '' | |
| : defaultDateNull | |
| ? null | |
| : defaultDateUndefined | |
| ? undefined | |
| : (dschema as z.ZodDate).minDate | |
| return value | |
| } | |
| if (dschema instanceof z.ZodSymbol) return '' | |
| if (dschema instanceof z.ZodBoolean) return false | |
| if (dschema instanceof z.ZodNull) return null | |
| if (dschema instanceof z.ZodPipeline) { | |
| if (!('out' in dschema._def)) return undefined // error | |
| return getDefaultValue(dschema._def.out) | |
| } | |
| if (dschema instanceof z.ZodObject) { | |
| return defaultInstance(dschema, options) | |
| } | |
| if (dschema instanceof z.ZodAny && !('innerType' in dschema._def)) return undefined // error? | |
| return getDefaultValue(dschema._def.innerType) | |
| } | |
| } | |
| return run() | |
| } |
Update of ... non updates. I'm sorry that I haven't had time to come back to enhance this code. I was hoping that by now there would be a solution implemented in Zod, and have been holding out on continuing updates here. I'll hold out a bit longer. I haven't been active in my apps that use Zod recently so I'm a bit rusty on all of this.
The code here isn't ideal. There are a number of faults in more complex use cases. Here is my suggested approach to addressing this topic:
- Watch this gist, but don't count on it being updated or for generous submissions to be incorporated.
- Watch colinhacks/zod#1953
- Be aware that any system that provides a default value for general use cases may not be ideal for a large number of specific use cases.
- Get lots of sleep. Drink lots of coffee. Be good to yourself and others.
- Have a great day.
Anyone have a chance to see if the new zod version improves this experience?
Update: Upgrading to
zod@4beta breaks this function and the new version doesn't appear to solve the default schema problem
For the veterans of this gist: zod v4 compatible edition
import { z } from 'zod';
/**
* @summary Function returns default object from Zod schema (Zod v4 compatible)
* @version 24.0.0
* @link https://gist.github.com/TonyGravagno/2b744ceb99e415c4b53e8b35b309c29c
* @author Jacob Weisenburger, Josh Andromidas, Thomas Moiluiavon, Tony Gravagno, Vladislav Markov
* @param schema Zod schema definition
* @param options Optional configuration object
* @param options.defaultArrayEmpty If true, arrays return empty array; if false, array with one default element
* @param options.defaultDateEmpty If true, dates return empty string
* @param options.defaultDateUndefined If true, dates return undefined
* @param options.defaultDateNull If true, dates return null
* @returns Object of type schema with defaults for all fields
* @example
* const schema = z.object( { ... } )
* const default1 = defaultInstance<typeof schema>(schema)
* const default2 = defaultInstance<typeof schema>(
* schema,{ // toggle from these defaults if required
* defaultArrayEmpty: false,
* defaultDateEmpty: false,
* defaultDateUndefined: false,
* defaultDateNull: false,
* } )
*/
export interface DefaultInstanceOptions {
defaultArrayEmpty?: boolean;
defaultDateEmpty?: boolean;
defaultDateUndefined?: boolean;
defaultDateNull?: boolean;
}
export function defaultInstance<T extends z.ZodType>(schema: z.ZodType, options: DefaultInstanceOptions = {}): z.infer<T> {
const {
defaultArrayEmpty = false,
defaultDateEmpty = false,
defaultDateUndefined = false,
defaultDateNull = false,
} = options;
function isZodObject(s: z.ZodType): s is z.ZodObject<any> {
return s instanceof z.ZodObject || (s && typeof s === 'object' && 'shape' in s && typeof (s as any).shape === 'object');
}
function run(): z.infer<T> {
if (schema instanceof z.ZodTransform) {
const def = (schema as any)._def;
if (def && 'schema' in def) {
const innerSchema = def.schema as z.ZodType;
return defaultInstance(innerSchema, options);
}
}
if (isZodObject(schema)) {
const shape = schema.shape;
if (!shape) {
return {} as z.infer<T>;
}
return Object.fromEntries(
Object.entries(shape).map(([key, value]) => {
const val = value as z.ZodType;
const this_default = val instanceof z.ZodTransform ? defaultInstance(val, options) : getDefaultValue(val);
return [key, this_default];
}),
) as z.infer<T>;
}
return getDefaultValue(schema) as z.infer<T>;
function getDefaultValue(dschema: z.ZodType): any {
function extractDefaultValue(s: z.ZodDefault | z.ZodPrefault): any {
const def = (s as any)._def;
if (def && 'defaultValue' in def) {
const defaultValue = def.defaultValue as unknown;
return typeof defaultValue === 'function' ? (defaultValue as () => any)() : defaultValue;
}
return getDefaultValue(s.unwrap() as z.ZodType);
}
function getArrayElementType(arraySchema: z.ZodArray<any>): z.ZodType | null {
if ((arraySchema as any).element) {
return (arraySchema as any).element as z.ZodType;
}
const def = (arraySchema as any)._def;
if (def && 'element' in def) {
return def.element as z.ZodType;
}
return null;
}
if (isZodObject(dschema)) {
return defaultInstance(dschema, options);
}
if (dschema instanceof z.ZodDefault) {
return extractDefaultValue(dschema);
}
if (dschema instanceof z.ZodPrefault) {
return extractDefaultValue(dschema);
}
if (dschema instanceof z.ZodOptional) {
return getDefaultValue(dschema.unwrap() as z.ZodType);
}
if (dschema instanceof z.ZodNullable) {
return getDefaultValue(dschema.unwrap() as z.ZodType);
}
if (dschema instanceof z.ZodArray) {
const elementType = getArrayElementType(dschema);
if (elementType) {
return defaultArrayEmpty ? [] : [getDefaultValue(elementType)];
}
return [];
}
if (dschema instanceof z.ZodString) return '';
if (dschema instanceof z.ZodNumber || dschema instanceof z.ZodBigInt) {
return (dschema as any).minValue ?? 0;
}
if (dschema instanceof z.ZodDate) {
return defaultDateEmpty
? ''
: defaultDateNull
? null
: defaultDateUndefined
? undefined
: (dschema as any).minDate;
}
if (dschema instanceof z.ZodSymbol) return '';
if (dschema instanceof z.ZodBoolean) return false;
if (dschema instanceof z.ZodNull) return null;
if (dschema && typeof dschema === 'object' && '_def' in dschema && 'out' in (dschema as any)._def) {
const pipeOut = (dschema as any)._def.out as z.ZodType;
if (pipeOut) {
return getDefaultValue(pipeOut);
}
}
if (dschema instanceof z.ZodRecord) {
return {};
}
if (dschema instanceof z.ZodAny || dschema instanceof z.ZodUnknown || dschema instanceof z.ZodCustom) {
return undefined;
}
if (dschema && typeof dschema === 'object' && '_def' in dschema) {
const def = (dschema as any)._def;
if ('innerType' in def && def.innerType) {
return getDefaultValue(def.innerType as z.ZodType);
}
if ('unwrap' in dschema && typeof dschema.unwrap === 'function') {
try {
return getDefaultValue(dschema.unwrap() as z.ZodType);
} catch {
// ignore
}
}
}
return undefined;
}
}
return run();
}total: z.number() defaults to total: -Infinity
fix:
if (dschema instanceof z.ZodNumber || dschema instanceof z.ZodBigInt) {
const minValue = (dschema as any).minValue
return minValue && minValue !== -Infinity ? minValue : null
}
Thanks for your contribution, I really appreciate it.