Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Incorrect error for unions with literals #311

Closed
aliksend opened this issue Feb 4, 2021 · 8 comments
Closed

Incorrect error for unions with literals #311

aliksend opened this issue Feb 4, 2021 · 8 comments

Comments

@aliksend
Copy link

aliksend commented Feb 4, 2021

Description
I use zod@2.0.0-beta.30
I want to use zod to parse structures like

type T = { val: 'a', a?: string } | { val: 'b', b?: number }

but I get incorrect error when I tries to parse structure with incorrect field's type

Demo

import * as z from 'zod'

const Schema = z.union([
  z.object({
    val: z.literal('a'),
    a: z.ostring()
  }),
  z.object({
    val: z.literal('b'),
    b: z.onumber()
  })
])

console.log(Schema.parse({
  val: 'a',
  a: 123         
}))

Expected error

Error: [
  {
    "code": "invalid_type",
    "expected": "string",
    "received": "number",
    "path": [
      "a"
    ],
    "message": "Expected string, received number"
  }
]

Actual error

Error: [
  {
    "code": "invalid_literal_value",
    "expected": "b",
    "path": [
      "val"
    ],
    "message": "Input must be \"b\""
  }
]
@alexcouret
Copy link

I'm having the same issues, without literals. Basically any union types containing objects seems to yield wrong error messages.

Tried with zod@1.11.1 and zod@next.
It seems like it builds up errors as it tries to parse each type and is not able to choose the most relevant one.

Here's another example:

import * as zod from zod;

const schema = zod.union([
    zod.object({
        a: zod.string(),
    }),
    zod.object({
        b: zod.boolean()
    })
]);

schema.parse({ b: 'test' });

@colinhacks
Copy link
Owner

Zod isn't able to guess which schema in the union is most "relevant". This just isn't possible in the general case. It may seem obvious to you that the val literals are being used as the discriminator for the union, but how would Zod be able to figure that out? Consider the general case — what if there were multiple keys associated with literal values, etc. This logic gets stupidly complicated really fast and just makes it harder for people to understand what's going on.

The real mystery here is why you received a "invalid_literal_value" error. You should have received an "invalid_union" that contains the errors thrown by each schema within the union. @aliksend when I run the code you provided this is the error I get: (this is on Zod 3 so there will be some small discrepancies but the top-level error should be invalid_union.

Error: [
  {
    "code": "invalid_union",
    "unionErrors": [
      {
        "issues": [
          {
            "code": "invalid_type",
            "expected": "string",
            "received": "number",
            "path": [
              "a"
            ],
            "message": "Expected string, received number"
          }
        ]
      },
      {
        "issues": [
          {
            "code": "invalid_type",
            "expected": "b",
            "received": "a",
            "path": [
              "val"
            ],
            "message": "Expected b, received a"
          }
        ]
      }
    ],
    "path": [],
    "message": "Invalid input"
  }
]

@aliksend
Copy link
Author

@colinhacks you can use this repl to investigate issue.
But as I undestand it is no longer relevant.
The invalid_union error is a good solution for errors like this.
Preparing for the transition to v3! Thanks for your work

@hmaurer
Copy link

hmaurer commented Apr 12, 2021

I have been using Zod 3 and poor error messages when using discriminated unions lead me to this issue. As @colinhacks pointed out above Zod isn't able to guess which schema is most "relevant", but has it been considered to add an explicit "discriminated union" construct to Zod? JSON Type Definition has a discriminator form for specifically this purpose, e.g.

{
  discriminator: "val",
  mappings: {
    "a": {
      properties: {
        a: {type: "string"}
      }
    },
    "b": {
      properties: {
        b: {type: "number"}
      }
    }
  }
}

There's an AJV implementation as well. In Zod, it could look something like:

z.discriminatedUnion("val", {
  a: { a: z.string() },
  b: { b: z.number() },
});

If there are no immediate objections I'd be happy to work on an implementation.

@colinhacks
Copy link
Owner

@hmaurer I think this is a very interesting concept. I like that API and I'd be very interested in a PR!

@colinhacks
Copy link
Owner

@hmaurer I'm closing this but if you want to create a separate issue for discriminated unions feel free.

@ottokruse
Copy link

If there are no immediate objections I'd be happy to work on an implementation.

Any luck with that @hmaurer ?

@alexxander
Copy link
Contributor

I implemented a function for handling discriminated unions (using zod 3.11.6).
zodDiscriminatedUnion(discriminator: string, types: ZodObject[])

  • discriminator the name of the discriminator property
  • types an array of object schemas
    Its behaviour is very similar to that of the z.union() function. However, it only allows a union of objects, all of which need to share a discriminator property. This property must have a different value for each object in the union.

More info here: #792 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants