Open derekparsons718 opened 3 years ago
This workaround removes the error...
interface A {
a: number
}
interface B {
b: string
}
type C = A | B;
const aSchema: toZod<A> = z.object({a: z.number()})
const bSchema: toZod<B> = z.object({b: z.string()})
const cSchema: z.ZodUnion<[toZod<A>, toZod<B>]> = z.union([aSchema, bSchema]) //Note the new type of cSchema
... but the type of cSchema
no longer references C
at all, which means that cSchema
could easily fall out of sync with C
and cause validation problems. This is far from an ideal solution.
@colinhacks any chance you'll implement support for unions anytime soon?
+1
Tying in vain to get this working (or some variation of it)
type PipelineProvider = 'bitbucket' | 'github'
type AddPipelineData = {
provider: PipelineProvider
repositoryName: string
}
const bitbucket = z.literal('bitbucket')
const github = z.literal('github')
const addPipelineValidationSchema: toZod<AddPipelineData> = z.object({
provider: z.union([bitbucket, github]),
repositoryName: z.string().min(1, { message: 'Repository name is required' }),
})
EDIT: this is fixed in https://github.com/colinhacks/zod/issues/1495#issuecomment-1339832685
I've been banging my head with this issue, the main problem I'm facing is how to support the f-king syntax of Zod that expects an Union<[A, ...A]>
. In the meantime, I added a patch that turns "unions" into unions of any:
import * as z from 'zod';
declare type isAny<T> = [any extends T ? 'true' : 'false'] extends ['true'] ? true : false
declare type equals<X, Y> = [X] extends [Y] ? ([Y] extends [X] ? true : false) : false
export declare type toZodder<T> =
isAny<T> extends true ? z.ZodAny :
[T] extends [boolean] ? z.ZodBoolean :
[undefined] extends [T] ?
T extends undefined ? never : z.ZodOptional<toZodder<T>> :
[null] extends [T] ?
T extends null ? z.ZodNull : z.ZodNullable<toZodder<T>> :
T extends Array<infer U> ? z.ZodArray<toZodder<U>> :
T extends Promise<infer U> ? z.ZodPromise<toZodder<U>> :
equals<T, string> extends true ? z.ZodString :
equals<T, bigint> extends true ? z.ZodBigInt :
equals<T, number> extends true ? z.ZodNumber :
equals<T, Date> extends true ? z.ZodDate :
T extends { [k: string]: any; } ? z.ZodObject<{ [k in keyof T]-?: toZodder<T[k]>; }, 'strip', z.ZodTypeAny, T, T> :
(T extends any ? (k: T)=>void : never) extends ((k: infer I)=>void) ? z.ZodUnion<Readonly<[z.ZodTypeAny, ...z.ZodTypeAny[]]>>
: z.ZodAny;
The line (T extends any ? (k: T)=>void : never) extends ((k: infer I)=>void) ? z.ZodUnion<Readonly<[z.ZodTypeAny, ...z.ZodTypeAny[]]>>
is the Union to intersection logic added.
I am also needing support for unions.
For example. this should work:
type TestType = {
type: "a" | "b";
};
export const TestSchema: toZod<TestType> = z.object({
type: z.union([z.literal("a"), z.literal("b")]),
});
But it throws this error:
Type 'ZodObject<{ type: ZodUnion<[ZodLiteral<"a">, ZodLiteral<"b">]>; }, "strip", ZodTypeAny, { type: "a" | "b"; }, { type: "a" | "b"; }>' is not assignable to type 'ZodObject<{ type: never; }, "strip", ZodTypeAny, TestType, TestType>'.
Type '{ type: z.ZodUnion<[z.ZodLiteral<"a">, z.ZodLiteral<"b">]>; }' is not assignable to type '{ type: never; }'.
Types of property 'type' are incompatible.
Type 'ZodUnion<[ZodLiteral<"a">, ZodLiteral<"b">]>' is not assignable to type 'never'.ts(2322)
Give this a try: colinhacks/zod#1495 (comment)
Using the new TypeScript satisfies
works great, but it doesn't check for null
, undefined
, or optional properties (?
).
type TestType = {
foo?: "a" | "b";
};
export const TestSchema = z.object({
foo: z.union([z.literal("a"), z.literal("b")]), // This should produce an error because "foo" is optional, but no error is shown
}) satisfies z.ZodType<TestType>;
So I tried combining toZod
with satisfies
:
export const TestSchema: toZod<TestType> = z.object({
foo: z.union([z.literal("a"), z.literal("b")]).optional(),
}) satisfies z.ZodType<TestType>;
This should produce no errors because foo
is optional and it has .optional()
added, but it does produce this error:
Type 'ZodObject<{ foo: ZodOptional<ZodUnion<[ZodLiteral<"a">, ZodLiteral<"b">]>>; }, "strip", ZodTypeAny, { foo?: "a" | "b" | undefined; }, { ...; }>' is not assignable to type 'ZodObject<{ foo: ZodOptional<never> | ZodOptional<never>; }, "strip", ZodTypeAny, TestType, TestType>'.
Type '{ foo: z.ZodOptional<z.ZodUnion<[z.ZodLiteral<"a">, z.ZodLiteral<"b">]>>; }' is not assignable to type '{ foo: ZodOptional<never> | ZodOptional<never>; }'.
Types of property 'foo' are incompatible.
Type 'ZodOptional<ZodUnion<[ZodLiteral<"a">, ZodLiteral<"b">]>>' is not assignable to type 'ZodOptional<never> | ZodOptional<never>'.ts(2322)
Here's the workaround I've been using. I just use a little Equals
type helper to check if the original StringUnion type is identical to my Zod schema's type. This works for string unions, but you might need a more sophisticated implementation of Equals
to support comparing more complex types.
// The type that we want to build a Zod schema for.
type StringUnion = 'cat' | 'dog' | 'bat' | 'frog';
// Our Zod schema, which we want to stay in sync with StringUnion
const myStringUnion = z.union([
z.literal('cat'),
z.literal('dog'),
z.literal('bat'),
z.literal('frog'),
]);
type MyStringUnion = z.infer<typeof myStringUnion>;
// Type helpers to check if two types are the same.
type Equals<T, U> = [T] extends [U] ? ([U] extends [T] ? 1 : 0) : 0;
type Expect<T extends 1> = T;
// We will get a type error here if the two types ever diverge.
type Check = Expect<Equals<StringUnion, MyStringUnion>>;
Union types appear not to work as expected.
Example:
The above code causes this error:
Type 'ZodUnion<[ZodObject<{ a: ZodNumber; }, "passthrough", ZodTypeAny, A, A>, ZodObject<{ b: ZodString; }, "passthrough", ZodTypeAny, B, B>]>' is missing the following properties from type 'ZodObject<{ a: ZodNumber; } | { b: ZodString; }, "passthrough", ZodTypeAny, C, C>': _shape, _unknownKeys, _catchall, shape, and 15 more. ts(2740)
Is there a workaround for this? And is there any plan to support this kind of code in the future?