Deprecating z.discriminatedUnion. To be replaced by z.switch
#2109
JacobWeisenburger
started this conversation in
General
Replies: 2 comments 2 replies
-
|
Are there any plans to support this soon? |
Beta Was this translation helpful? Give feedback.
2 replies
-
|
Is this still being worked on? I love this proposed API and would support the addition of switch() |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
Originally posted by @colinhacks
#2106
I'm deprecating
z.discriminatedUnionin favor of a "switch" API that's cleaner and more generalizable. You can dynamically "switch" between multiple schemas at parse-time based on the input.I expand more on the
z.switchAPI later. Let's talk aboutz.discriminatedUnion.Why
z.unionnaively tries each union element until parsing succeeds. That's slow and bad. Zod needed some solution.z.discriminatedUnionwas a mistake. The originalz.discriminatedUnionAPI was merged without my approval. I'd entirely missed a long discussion and review process between other contributors and the PR author, and frankly I felt bad about coming in and undoing all that. The API was good but I had reservations about the implementation. It required fiddly recursive logic to exrtract a literal discriminator key from each union element.It's a bad sign when a method or class requires weird recursive traversal of other schemas. For starters, Zod is designed to be subclassable. Users can theoretically subclass
ZodTypeto implement custom schema types. But logic like thisinstanceofswitch statement don't and can't account for any user-land schema types.But the main problem is just that this kind of pattern is bad and introduces a lot of edge cases. It means that only certain kinds of schemas are allowed as discriminators, and others will fail in unexpected ways. There are now dozens of issues that have been opened regarding these various edge cases. The PRs attempting to solve this problem are irredeemably complex and introduce even more edge cases.
discriminatedUnionwithlazy? #1504discriminatedUnionerrors withz.object().transform()#1477.union()vs.discriminatedUnion()#1424discriminatedUnionproduces TS error when.defaultor.preprocessare applied #1490Many of those issues are asking for non-literal discriminator types:
Imagine each of those elements are represented with Zod schemas. Zod would need to extract the
typefield from each of these elements and find a way to match the incominginput.typeagainst those options. In the general case, Zod would extract thetypefield from the shape of each componentZodObjectand checkinput.typeagainst those schemas until a match is found. At that point, we're back to doing a parse operation for each element of the union, which is whatz.discriminatedUnionis supposed to avoid doing. (It's still doing less work than the naivez.unionbut still.)Another issue is composability. The existing API expects the second argument to be an array of
ZodObjectschemas.This isn't composable, in that you can't nest discriminated unions or add additional members.
Yes, Zod could support both
(ZodObject | ZodDiscriminatedUnion)[]as union members, but that requires additional messy logic that reflects a more fundamental problem with the API. It also makes increasingly difficult to enforce typesafety on the union - it's important that all union elements have atypeproperty, otherwise the union is no longer discriminable.Replacement:
z.switchA discriminated union looks like this:
Ultimately the
z.switchAPI is a far more explicit and generalizable API. Zod doesn't do any special handling. The user specifies exactly how theinputwill be used to select the schema.z.switch()accepts a function. TheResultTypeof that function is inferred. It will be the union of the schema types returned along all code paths in the function. For instance:Zod sees that the return type of the switcher function is
ZodString | ZodNumber. The result of thez.switchisZodSwitch<ZodString | ZodNumber>. The result ofschema.parse(...)isstring | number.You can represent discriminated unions explicitly like this:
This can be written in a more condensed form like so:
It's marginally more verbose. It's also explicit, closes 30+ issues, eliminates a lot of hairy logic, and lets Zod represent the full scope of TypeScript's type system.
z.discrimininatedUnionis too fragile and causes too much confusion so it needs to go.Beta Was this translation helpful? Give feedback.
All reactions