Skip to content

Instantly share code, notes, and snippets.

@adnanalbeda
Last active May 27, 2024 01:06
Show Gist options
  • Save adnanalbeda/d9ba2a44553251593553832eea8e88cc to your computer and use it in GitHub Desktop.
Save adnanalbeda/d9ba2a44553251593553832eea8e88cc to your computer and use it in GitHub Desktop.
Zod Utils

Zod Utils

Zod is a great library for shaping and validating data. So away from the long introductory, it misses few feature.

People tried to fix its issues with extended libraries.

But since some are not maintained or didn't meet my criteria, here's my fine-tuned version of them.

If something doesn't work, please comment. I might come back, or might not.

A library made to enhance intellisense while defining z rules. Not continued, so here you go with my changes.

My changes are for supporting union, default and refine (ZodEffect) rules.

A gist made to build default value based on zod rules.

Notes

(nullable, optional, refine, default) Rules Order

I struggled with these rules with other utils and validation, so here is my summary. They must come at the end after defining all other rules in this order when any/all are used:

z.string() // or number() or object({}) ...
  .nullable() // always comes at last and before optional.
  .optional() // always comes at last and before default.
  .refine(_=>true) // custom validation after all built-in rules.
  .defatul("") // always comes at last.
import { z } from "zod";
type IsAny<T> = [any extends T ? "true" : "false"] extends ["true"]
? true
: false;
type NonOptional<T> = T extends undefined ? never : T;
type equals<X, Y> = [X] extends [Y] ? ([Y] extends [X] ? true : false) : false;
export type toZod<T> = {
any: z.ZodTypeAny;
optional: z.ZodOptional<ToZod<NonOptional<T>>>;
nullable: z.ZodNullable<ToZod<NonNullable<T>>>;
array: T extends Array<infer U> ? z.ZodArray<ToZod<U>> : never;
string: z.ZodString;
bigint: z.ZodBigInt;
number: z.ZodNumber;
boolean: z.ZodBoolean;
date: z.ZodDate;
object: z.ZodObject<{ [k in keyof T]: ToZod<T[k]> }>;
}[zodKey<T>];
type zodKey<T> = IsAny<T> extends true
? "any"
: undefined extends T
? "optional"
: null extends T
? "nullable"
: equals<T, string> extends true
? "string"
: equals<T, bigint> extends true
? "bigint"
: equals<T, number> extends true
? "number"
: equals<T, boolean> extends true
? "boolean"
: equals<T, Date> extends true
? "date"
: T extends any[]
? "array"
: T extends { [k: string]: any }
? "object"
: "any";
export type ToZod<T> =
| toZod<T>
| z.ZodDefault<toZod<T>>
| z.ZodUnion<[toZod<T> | z.ZodDefault<toZod<T>>, ...z.ZodTypeAny[]]>
| z.ZodEffects<
| toZod<T>
| z.ZodDefault<toZod<T>>
| z.ZodUnion<[toZod<T> | z.ZodDefault<toZod<T>>, ...z.ZodTypeAny[]]>
>;
import { z } from "zod";
export const zodSchemaDefaults = <Schema extends z.ZodFirstPartySchemaTypes>(
schema: Schema,
): z.TypeOf<Schema> => {
switch (schema._def.typeName) {
// has default
case z.ZodFirstPartyTypeKind.ZodDefault:
return schema._def.defaultValue();
// can be null
case z.ZodFirstPartyTypeKind.ZodOptional:
return undefined;
case z.ZodFirstPartyTypeKind.ZodNullable:
case z.ZodFirstPartyTypeKind.ZodNull:
return null;
// primitive
case z.ZodFirstPartyTypeKind.ZodBoolean:
return false;
case z.ZodFirstPartyTypeKind.ZodString:
return "";
case z.ZodFirstPartyTypeKind.ZodBigInt:
case z.ZodFirstPartyTypeKind.ZodNumber:
return 0;
case z.ZodFirstPartyTypeKind.ZodNaN:
return NaN;
case z.ZodFirstPartyTypeKind.ZodDate:
return new Date();
// complex shape
case z.ZodFirstPartyTypeKind.ZodArray:
return [];
case z.ZodFirstPartyTypeKind.ZodObject: {
// The switch wasn't able to infer this but the cast should
// be safe.
return Object.fromEntries(
Object.entries((schema as z.SomeZodObject).shape).map(
([key, value]) => [key, schemaDefaults(value)],
),
);
}
case z.ZodFirstPartyTypeKind.ZodEffects: {
return schemaDefaults(schema._def.schema);
}
// etc
default:
throw new Error(`Unsupported type ${schema._type}`);
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment