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
-
Originally posted by @colinhacks
#2106
I'm deprecating
z.discriminatedUnion
in 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.switch
API later. Let's talk aboutz.discriminatedUnion
.Why
z.union
naively tries each union element until parsing succeeds. That's slow and bad. Zod needed some solution.z.discriminatedUnion
was a mistake. The originalz.discriminatedUnion
API 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
ZodType
to implement custom schema types. But logic like thisinstanceof
switch 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.
discriminatedUnion
withlazy
? #1504discriminatedUnion
errors withz.object().transform()
#1477.union()
vs.discriminatedUnion()
#1424discriminatedUnion
produces TS error when.default
or.preprocess
are 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
type
field from each of these elements and find a way to match the incominginput.type
against those options. In the general case, Zod would extract thetype
field from the shape of each componentZodObject
and checkinput.type
against 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.discriminatedUnion
is supposed to avoid doing. (It's still doing less work than the naivez.union
but still.)Another issue is composability. The existing API expects the second argument to be an array of
ZodObject
schemas.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 atype
property, otherwise the union is no longer discriminable.Replacement:
z.switch
A discriminated union looks like this:
Ultimately the
z.switch
API is a far more explicit and generalizable API. Zod doesn't do any special handling. The user specifies exactly how theinput
will be used to select the schema.z.switch()
accepts a function. TheResultType
of 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.switch
isZodSwitch<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.discrimininatedUnion
is too fragile and causes too much confusion so it needs to go.Beta Was this translation helpful? Give feedback.
All reactions