Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is there a way to combine discriminatedUnion with lazy? #1504

Closed
LuxXx opened this issue Oct 18, 2022 · 6 comments
Closed

Is there a way to combine discriminatedUnion with lazy? #1504

LuxXx opened this issue Oct 18, 2022 · 6 comments
Labels
stale No activity in last 60 days

Comments

@LuxXx
Copy link

LuxXx commented Oct 18, 2022

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

@itsgiacoliketaco
Copy link

itsgiacoliketaco commented Oct 18, 2022

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.

@LuxXx
Copy link
Author

LuxXx commented Oct 19, 2022

This does not work for me. I cannot define recursiveElement without lazy.

@itsgiacoliketaco
Copy link

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()?

@roblabat
Copy link
Contributor

roblabat commented Oct 28, 2022

I've got this openned PR #1290 fixing this issue and waiting for feed back or merge.

@stale
Copy link

stale bot commented Jan 26, 2023

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.

@stale stale bot added the stale No activity in last 60 days label Jan 26, 2023
@stale stale bot closed this as completed Feb 25, 2023
@mifeet
Copy link

mifeet commented Mar 23, 2024

See also this Stack Overflow answer that provides a different solution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
stale No activity in last 60 days
Projects
None yet
Development

No branches or pull requests

4 participants