Nested discriminated unions
See original GitHub issueI have a hierarchy of objects, where the first level uses a type
-property to discriminate, and the second level uses a subtype
.
An example diagram showing 3 instances on the first level (Foo, Bar & Baz), and then Baz having two subtypes under it (Able & Baker):
[root]
/ | \
Foo Bar Baz
/ \
Able Baker
It’s possible to type this in TS with something like
type NestedDu =
{ type: "foo" }
| { type: "bar" }
| { type: "baz", subtype: "able" }
| { type: "baz", subtype: "baker" };
(Of course there are more fields on each property, but just keeping it minimal for the ease of understanding)
I tried to construct a Zod-schema using the following
const nestedDU = z.discriminatedUnion('type', [
z.object({
"type": z.literal("foo")
}),
z.object({
"type": z.literal("bar")
}),
z.discriminatedUnion('subtype', [
z.object({
"type": z.literal("baz"),
"subtype": z.literal("able")
}),
z.object({
"type": z.literal("baz"),
"subtype": z.literal("baker")
}),
]),
]);
But this doesn’t work, since the outer discriminated union doesn’t allow the inner one.
Is there any way to make this work as-is - or would it need additional work on Zod itself to make it work? I can try to create a PR if needed, but maybe it’s already possible and I’m just missing something.
Kind regards Morten
Issue Analytics
- State:
- Created 10 months ago
- Comments:12 (4 by maintainers)
Top GitHub Comments
My half-though with the array of discriminators was that it could potentially be something that could be checked with a recursive type-definition My thinking would be to recursively loop over the discriminators and the properties and match them up - if we exhaust the properties before the discriminators, then type type should resolve to
never
. I believe this should be achievable with splitting the list up into the head & tail and seeing if the tail is empty.Another option is to look into getting
ZodObject.merge
to accept a discriminate union as the input, because then this would be achievableIt currently fails because
ZodDiscriminateUnion
isn’t assignable toAnyZodObject
. I guess the semantics are a little weird since then.merge
would end up basically converting the original object to a union.So semantically it’s probably nicer to just have DUs nest the way you naively expect them to - i.e. adding the inner DU to the list of objects in the outer DU.
I ran your PR-branch locally, and can confirm that it worked exactly like I’d hope it would. Wonderful work!