question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Nested discriminated unions

See original GitHub issue

I 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:open
  • Created 10 months ago
  • Comments:12 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
fangelcommented, Dec 5, 2022

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 achievable

z.discriminatedUnion('type', [
    z.object({
        "type": z.literal("foo")
    }),
    z.object({
        "type": z.literal("bar")
    }),
    z.object({
        "type": z.literal("baz")
    }).merge(z.discriminatedUnion('subtype', [
        z.object({
            "subtype": z.literal("able")
        }),
        z.object({
            "subtype": z.literal("baker")
        }),
    ])),
]);

It currently fails because ZodDiscriminateUnion isn’t assignable to AnyZodObject. 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.

0reactions
fangelcommented, Dec 12, 2022

I ran your PR-branch locally, and can confirm that it worked exactly like I’d hope it would. Wonderful work!

Read more comments on GitHub >

github_iconTop Results From Across the Web

F#: Nested discriminated unions and matching - Stack Overflow
I have 2 nested discriminated unions:
Read more >
ValueError raised when Nested Discriminated Unions are ...
In my case the error appears when I use an Annotated field twice in the same model. Using Pydantic v1.9.0. from typing import...
Read more >
The case for Discriminated Union Types with Typescript
Discriminated Unions combine more than one technique and create self-contained types. Types that carry all the information to use them without ...
Read more >
Discriminated Unions - F# | Microsoft Learn
Discriminated unions provide support for values that can be one of a number of named cases, possibly each with different values and types....
Read more >
Lesson 21. Modeling relationships in F# - Get Programming ...
Listing 21.6. Nested discriminated unions. type MMCDisk = #1 | RsMmc | MmcPlus | SecureMMC type Disk = | MMC of MMCDisk *...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found