Function to create a union of literal dynamically #2790
-
|
Currently, it does not seem to be possible to create a function that does union of literal, like: function unionOfLiterals<T extends string | number>(constants: readonly T[]): z.ZodUnion<[ZodTypeAny, ...ZodTypeAny[]]> {
const literals = constants.map((constant) => z.literal(constant)) as [z.ZodLiteral<T>, ...z.ZodLiteral<T>[]];
return z.union(literals);
}
const MY_CONSTANTS = ['a', 'b', 1] as const;
const unionType = createUnionType(MY_CONSTANTS);This code gives the following typing error: And a call to this method returns a z.ZodUnion<[ZodTypeAny, ...ZodTypeAny[]]> object. Providing the constants directly is straightfoward: z.union([z.literal('a'), z.literal('b'), z.literal(1)])If that was possible, it would enable several use cases for using solely zod for API input validation. Now I end up losing typing when doing that indirectly. Amazing library, by the way. |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 5 replies
-
|
Is this what you are looking for? function unionOfLiterals<T extends string | number> ( constants: readonly T[] ) {
const literals = constants.map(
x => z.literal( x )
) as unknown as readonly [ z.ZodLiteral<T>, z.ZodLiteral<T>, ...z.ZodLiteral<T>[] ]
return z.union( literals )
}
const MY_CONSTANTS = [ 'a', 'b', 1 ] as const
const unionType = unionOfLiterals( MY_CONSTANTS )
type UnionType = z.infer<typeof unionType>
// type UnionType = "a" | "b" | 1
console.log( unionType.parse( 'a' ) ) // a
console.log( unionType.parse( 'b' ) ) // b
console.log( unionType.parse( 1 ) ) // 1
console.log( unionType.safeParse( 'foo' ).success ) // falseIf you found my answer satisfactory, please consider supporting me. Even a small amount is greatly appreciated. Thanks friend! 🙏 |
Beta Was this translation helpful? Give feedback.
-
// ═══╡ 🧩 IMPORTS ╞═══
import { z } from 'zod'
const MIN_VALUES = 2
/*
*╭───═══◎◎◎═══───═══◎◎◎═══───═══◎◎◎═══───═══◎◎◎═══───═══◎◎◎═══───═══◎◎◎═══───
*🌍 HELPER FUNCTIONS ► Zod schema helpers for type-safe domain modeling
*╰───═══◎◎◎═══───═══◎◎◎═══───═══◎◎◎═══───═══◎◎◎═══───═══◎◎◎═══───═══◎◎◎═══───
*/
/**
* Creates a Zod literal union from a const object's values.
*
* Enterprise Pattern for type-safe domain constants without manual union construction.
* Follows industry standards from tRPC, Prisma, and TypeScript Compiler projects.
*
* @example String domain
* ```ts
* const INSURANCE_TYPE = { gkv: 'gkv', pkv: 'pkv' } as const
* const schema = createLiteralUnion(INSURANCE_TYPE)
* // Validates: 'gkv' | 'pkv'
* ```
* @example Numeric domain
* ```ts
* const INSURANCE_STATUS = { agreement: 0, unknown: 9 } as const
* const schema = createLiteralUnion(INSURANCE_STATUS)
* // Validates: 0 | 9
* ```
* @param constObj - Const object with `as const` (values must be string or number).
* @returns Zod union of literals derived from object values.
* @throws {@link TypeError} If constObj has fewer than 2 values (Zod z.union requirement).
* @remarks
* Type assertion rationale: TypeScript cannot infer tuple structure from
* Object.values().map() at compile-time. Runtime correctness is guaranteed by
* defensive validation. This approach is standard in TypeScript Compiler, Zod,
* and tRPC codebases for utility functions with compile-time known inputs.
* @see {@link https://zod.dev/?id=unions | Zod Unions Documentation}
* @see {@link https://zod.dev/?id=literals | Zod Literals Documentation}
*/
export const createLiteralUnion = <T extends Record<string, number | string>>(
constObj: Readonly<T>
): z.ZodUnion<
readonly [z.ZodLiteral<T[keyof T]>, z.ZodLiteral<T[keyof T]>, ...z.ZodLiteral<T[keyof T]>[]]
> => {
const values = Object.values(constObj)
if (values.length < MIN_VALUES) {
throw new TypeError(
`createLiteralUnion requires at least ${String(MIN_VALUES)} values..
Received: ${String(values.length)}. Keys: ${Object.keys(constObj).join(', ')}`
)
}
const literals = values.map(value => z.literal(value))
/**
* The type of the literal tuple.
*/
type LiteralTuple = readonly [
z.ZodLiteral<T[keyof T]>,
z.ZodLiteral<T[keyof T]>,
...z.ZodLiteral<T[keyof T]>[]
]
return z.union(literals as unknown as LiteralTuple)
} |
Beta Was this translation helpful? Give feedback.
Is this what you are looking for?
If you found my answer satisfacto…