Tango - I have the need to handle discriminated...
I have the need to handle discriminatedUnion and enums with an "Unkown" option. Is there a way to do something like that in a native way?
I wrote a bit about the problem I like to solve in more detail here:
the_problem_of_typesafe_parsing_the_unknown_with_zod
Solution:Jump to solution
Thats fine, I guess either way, we need a bunch of dynamic code, if we like it to behave as desired.
I think within code generation we have some options, but nothing really streight forward. Thanks again....
17 Replies
Discriminated unions in TypeScript don't work that way, so discriminated unions in Zod don't either. 😅
I know this seems like a flippant reply, but it's the only honest thing. You can use
union
and just deal with the fact that TypeScript cannot narrow since this isn't a discriminated union, or you can have a multi-layered approach, which is what I typically do.
Basically parse the data with the most strict schema, and if it passes, great! You now have the known, useful type. If it fails, try parsing it with a more lenient schema (like UnknownPet
) in your example. Now you're forced by the compiler to deal with this loose type. What should you do in this case? Well, that is up to your logic: maybe this is a webhook handler that should ignore unknown types: log it and continue
. Maybe you need to throw a more useful error? We can't know, but keeping to this strict parsing means you are dealing with this ambiguity here rather than everywhere you pass the data downstream of this boundary.Discriminated unions in TypeScript don't work that way, so discriminated unions in Zod don't either. 😅I actually agree ^^.
What should you do in this case? Well, that is up to your logicExactly. Working with Zod on API / Integration layers, we need be able to let the application layer deal with the unknown. However, there is also the need to parse as strict as possible with well defined error messages. Currently I try to solve the issue with a z.custom logic. It is a bigger example, but I explan it with a little more detail here: https://github.com/dasaplan/ts-mono/blob/main/packages/openapi-tolerant-reader/docs/a_solution_for_typesafe_parsing_the_unknown.md#solution-creating-a-custom-zod-schema
FWIW, I think your "tolerant" schema here is no better than
union
yeah, error handling was something which I also thought of.
well, I guess it could be a little better than
union
at the moment since it short-circuits for the known types.
but at the type level you still get a non-discriminated union.yep, this is also an issue - with some intersection magic or tagged / opaque typing however, to me at least somewhat solvable
Another more principled approach is to make a known sentinel schema to "catch" these cases and transform them into the discrimination. That makes it at least somewhat easier to pass downstream.
interesting
Basically make a transform that maps it into your final discriminated type and just black boxes the data.
wouldn't a discrimnatedUnion already have blocked any unknown option?
TS Playground - An online editor for exploring TypeScript and JavaS...
The Playground lets you write TypeScript or JavaScript online in a safe and sharable way.
yeah, you cannot use the
discriminatedUnion
schema type here, you have to use union
. Maybe you can mix it with .or
Works fine too, and gives you a slight boost if your union contains many members
unfortunately, this is not good enough. I had similar thoughts. It will pass also for schemas, we know are incorrect.
From your example, you would rather try to transform to a value we know at compile time. I think, this is also the solution for several client / server code generators for their deserializers.
But all obviously need some "engine" or transformation logic to work the unknown type out.
For Javascript I heaven't really seen anything yet.
That seems correct to me since it's not discriminated.
I'm not sure I follow this question/comment. What's the specific issue with this kind of blackboxed approach?
oh, sry, nothing really. I just can't get it working like I would need it.
yeah. Maybe this is not something zod can / should solve.
Could be very specific for "deserialization".
Thanks for your insights!
Basically, you want
{ type: "Cat" }
to fail, but there is no way to reasonably do that statically without a bunch of dynamic code. In my personal opinion, I would embrace that and treat it the same as { type: "Spaceship", origin: "Alderaan" }
"These are values that are a superset of a known shape, but a completely opaque unknown
properties". And whatever you can reasonably do with that seems fine. Including going back and checking if they had a type from the known union and complaining.
I guess you could tweak my original "two phase parse" suggestion and examine the error from the strict parsing to see if it was { type: "Cat" }
-like.Solution
Thats fine, I guess either way, we need a bunch of dynamic code, if we like it to behave as desired.
I think within code generation we have some options, but nothing really streight forward. Thanks again.