close
Skip to content

Add typed callback function after parsing discriminate union #4019

Closed as not planned

Description

@NicolasCwy

Hi there, been using the library recently and love the typesafety! I have come across a use case though that I'm not sure how to go about, do let me know if there is some way to achieve it in the library, I am very new to using Zod

Following the example in the docs, I feel that there should be a way to type the parsed value for a discriminate union (not sure if the same can be applied for unions). Although I have parsed the value through my discriminate union schema, I have no type knowledge of what the value is although I believe, the parser has identified which of the items they are.

Current API

const myUnion = z.discriminatedUnion("status", [
  z.object({ status: z.literal("success"), data: z.string() }),
  z.object({ status: z.literal("failed"), error: z.instanceof(Error) }),
]);

const parsedData = myUnion.parse({ status: "success", data: "yippie ki yay" });
// parsedData.data? not possible

Proposed API
Similar to a .then which maps the schema name to a callback that will be executed if that schema was used for validation. This way we are able to parse the unknown type using the validator and then actually use what we know about it!

const success = { status: z.literal("success"), data: z.string() }
cosnt failed = z.object({ status: z.literal("failed"), error: z.instanceof(Error) })
const myUnion = z.discriminatedUnion("status", [
  success,
  failed,
]);

const successCallback = (s: success) => { console.log(s.data)}
const failedCallback = (f: failed) => { 
  console.log(f.error) 
  // console.log(f.data) will not work!
}


const parsedData = myUnion.parse({ status: "success", data: "yippie ki yay" });

parsedData.match({success: successCallback, failed: failedCallback})

Extra considerations
I think this probably will not be able to work on unions because a union might match multiple types therefore we are not able to know for sure what the actual item is? But for discriminateUnions which is the primary thing I am hoping this to be implemented for should be ok

Edit: I can see that if an object has nested discriminated types this might be quite a nightmare to handle. Not sure how this would be done

Activity

  1. changed the title [-]Add typed callback to discriminate union[/-] [+]Add typed callback function after parsing discriminate union[/+] on Mar 5, 2025
  2. BrendanC23 commented on Mar 6, 2025

    @BrendanC23

    This seems like it's a limitation of TypeScript. When you do

    const parsedData = myUnion.parse({ status: "success", data: "yippie ki yay" });

    parsedData is a discriminated union. You can't do parsedData.data because TypeScript doesn't know which union member this actually is. You would need to check that parsedData.status === "success" first.

    You would end up with the same issue if you did this directly in TypeScript and didn't use Zod:

    type MyUnion = { status: "success"; data: string } | { status: "failed"; error: Error };
    
    const data = { status: "success", data: "yippie ki yay" } as MyUnion;
    // Can access data.status but not data.data

    If you wanted to run your callback functions after parsing, can you do

    if (parsedData.status === "success") success(parsedData);
    else failure(parsedData);
    
  3. NicolasCwy commented on Mar 11, 2025

    @NicolasCwy
    Author

    @BrendanC23 This was what I did in the end as well. Not too familiar with writing complex types like in Zod but just wondering if it would be possible to use conditional typing to infer what the return type is, if one of the schema was evaluated to be true?

  4. BrendanC23 commented on Mar 11, 2025

    @BrendanC23

    You can do the following:

    const myUnion = z.discriminatedUnion("status", [
        z.object({ status: z.literal("success"), data: z.string() }),
        z.object({ status: z.literal("failed"), error: z.instanceof(Error) }),
    ]);
    
    type MyUnionType = z.input<typeof myUnion>;
    
    // MyUnionType  evaluates to
    type MyUnionType =
        | {
              status: "success";
              data: string;
          }
        | {
              status: "failed";
              error: Error;
          };
    
    type MyUnionSuccess = Extract<MyUnionType, { status: "success" }>;
    
    // MyUnionSuccess evaluates to
    type MyUnionSuccess = {
        status: "success";
        data: string;
    };
    
    type MyUnionFailed = Extract<MyUnionType, { status: "failed" }>;
    
    // MyUnionFailed evaluates to
    type MyUnionFailed = {
        status: "failed";
        error: Error;
    }
  5. NicolasCwy commented on Mar 12, 2025

    @NicolasCwy
    Author

    Hi @BrendanC23 I was suggesting a solution to my original post. I was asking if it was possible to use conditional inference to give a return type which can be used in the callback api I suggested

  6. dosubot commented on Jul 24, 2025

    @dosubot

    Hi, @NicolasCwy. I'm Dosu, and I'm helping the Zod team manage their backlog. I'm marking this issue as stale.

    Issue Summary:

    • You requested a feature to add a typed callback function for discriminated unions in Zod to enhance type safety.
    • @BrendanC23 suggested a workaround using TypeScript to check the status before executing callbacks.
    • You acknowledged the workaround but inquired about conditional typing for inferring return types in your proposal.
    • @BrendanC23 provided an example of extracting specific types from a discriminated union, but you sought further clarification.

    Next Steps:

    • Please let us know if this issue is still relevant to the latest version of the Zod repository. If so, you can keep the discussion open by commenting on the issue.
    • Otherwise, the issue will be automatically closed in 14 days.

    Thank you for your understanding and contribution!

  7. removed
    staleNo activity in last 60 days
    on Jul 31, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions