zod
zod copied to clipboard
Is there a way to combine `discriminatedUnion` with `lazy`?
Hello,
I stumbled on this problem.
When using lazy (recursive types) I cannot use discriminatedUnion anymore.
import z from "zod";
type SomeOtherObject = {
type: "some-other-type";
randomProperty: string;
};
const someOtherObjectWithADiscriminatingType = z.object({
type: z.literal("some-other-type"),
randomProperty: z.string()
});
type RecursiveElement = {
type: "recursive-element";
children: MyUnion[];
};
const recursiveElement: z.ZodType<RecursiveElement> = z.lazy(() =>
z.object({
type: z.literal("recursive-element"),
children: myUnion.array()
})
);
type MyUnion = RecursiveElement | SomeOtherObject;
What works is
const myUnion = z.union([
recursiveElement,
someOtherObjectWithADiscriminatingType
]);
But I would love to use discriminatedUnion here.
const myUnion = z.discriminatedUnion("type",[
recursiveElement,
someOtherObjectWithADiscriminatingType
]);
Any way I can type lazy object recursiveElement somehow that it does not throw an error?
Here is this problem in CodeSandBox: https://codesandbox.io/s/lucid-wilson-k5x1pu?file=/src/App.tsx:682-792
One option here is to make myUnion lazy instead of making the union elements lazy.
type MyUnion =
| z.infer<typeof someOtherObjectWithADiscriminatingType>
| RecursiveElement; // Can't use z.infer here, use explicit type
const myUnion: z.ZodType<MyUnion> = z.lazy(() =>
z.discriminatedUnion("type", [
recursiveElement,
someOtherObjectWithADiscriminatingType
]),
);
const someOtherObjectWithADiscriminatingType = z.object({
type: z.literal("some-other-type"),
randomProperty: z.string(),
});
const recursiveElement = z.object({
type: z.literal("recursive-element"),
children: myUnion.array(),
}); // With TS 4.9, could add `satisfies z.ZodType<RecursiveElement>` here
type RecursiveElement = {
type: "recursive-element";
children: MyUnion[]
}
(Edited to fix code)
In my experience this works nicely, because you only ever have one lazy schema (the union), even if you add additional recursive union elements.
This does not work for me. I cannot define recursiveElement without lazy.
I'm not sure if you have some different limitation that you're not describing, but I did realize my above suggestion was mistaken. You must additionally write a manual type definition for the RecursiveElement type:
type MyUnion =
| z.infer<typeof someOtherObjectWithADiscriminatingType>
| RecursiveElement; // Can't use z.infer here, use explicit type
const myUnion: z.ZodType<MyUnion> = z.lazy(() =>
z.discriminatedUnion("type", [
recursiveElement,
someOtherObjectWithADiscriminatingType
]),
);
const someOtherObjectWithADiscriminatingType = z.object({
type: z.literal("some-other-type"),
randomProperty: z.string(),
});
const recursiveElement = z.object({
type: z.literal("recursive-element"),
children: myUnion.array(),
}); // With TS 4.9, could add `satisfies z.ZodType<RecursiveElement>` here
type RecursiveElement = {
type: "recursive-element";
children: MyUnion[]
}
This definitely works (I've used this pattern myself). Do you have some other reason why you can't define recursiveElement without z.lazy()?
I've got this openned PR https://github.com/colinhacks/zod/pull/1290 fixing this issue and waiting for feed back or merge.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
See also this Stack Overflow answer that provides a different solution.