What is a good strategy to get a nice (nicer) error for invalid discriminated unions
See original GitHub issueFirst of all: thanks for creating zod
it is an awesome library.
Consider the following schema (contrived examples of course), and test. When you run this the error message is really long and hard for humans to grok. Note the schema has a discriminated union (the field type
on each object is the discriminating field):
import { z } from "zod";
const typeA = z.object({
type: z.literal("A"),
prop1: z.string()
})
const typeB = z.object({
type: z.literal("B"),
prop2: z.string()
})
const typeC = z.object({
type: z.literal("C"),
prop3: z.string()
})
const typeD = z.object({
type: z.literal("D"),
prop4: z.string()
})
const schema = z.object({ mytypes: z.union([typeA, typeB, typeC, typeD]).array() });
schema.parse({
mytypes: [
{ type: "A", prop1: "foo" },
{ type: "D", bar: "BAR" } // misses prop4
]
});
The long and hard to read error message
ZodError: [
{
"code": "invalid_union",
"unionErrors": [
{
"issues": [
{
"code": "invalid_type",
"expected": "A",
"received": "D",
"path": [
"mytypes",
1,
"type"
],
"message": "Expected A, received D"
},
{
"code": "invalid_type",
"expected": "string",
"received": "undefined",
"path": [
"mytypes",
1,
"prop1"
],
"message": "Required"
}
],
"name": "ZodError"
},
{
"issues": [
{
"code": "invalid_type",
"expected": "B",
"received": "D",
"path": [
"mytypes",
1,
"type"
],
"message": "Expected B, received D"
},
{
"code": "invalid_type",
"expected": "string",
"received": "undefined",
"path": [
"mytypes",
1,
"prop2"
],
"message": "Required"
}
],
"name": "ZodError"
},
{
"issues": [
{
"code": "invalid_type",
"expected": "C",
"received": "D",
"path": [
"mytypes",
1,
"type"
],
"message": "Expected C, received D"
},
{
"code": "invalid_type",
"expected": "string",
"received": "undefined",
"path": [
"mytypes",
1,
"prop3"
],
"message": "Required"
}
],
"name": "ZodError"
},
{
"issues": [
{
"code": "invalid_type",
"expected": "string",
"received": "undefined",
"path": [
"mytypes",
1,
"prop4"
],
"message": "Required"
}
],
"name": "ZodError"
}
],
"path": [
"mytypes",
1
],
"message": "Invalid input"
}
]
at new ZodError (/private/tmp/zodtest/node_modules/zod/src/ZodError.ts:140:5)
at handleResult (/private/tmp/zodtest/node_modules/zod/src/types.ts:72:19)
at ZodObject.ZodType.safeParse (/private/tmp/zodtest/node_modules/zod/src/types.ts:184:12)
at ZodObject.ZodType.parse (/private/tmp/zodtest/node_modules/zod/src/types.ts:162:25)
at Object.<anonymous> (/private/tmp/zodtest/index.ts:25:8)
at Module._compile (node:internal/modules/cjs/loader:1101:14)
at Module.m._compile (/private/tmp/zodtest/node_modules/ts-node/src/index.ts:1371:23)
at Module._extensions..js (node:internal/modules/cjs/loader:1153:10)
at Object.require.extensions.<computed> [as .ts] (/private/tmp/zodtest/node_modules/ts-node/src/index.ts:1374:12)
at Module.load (node:internal/modules/cjs/loader:981:32) {
issues: [
{
code: 'invalid_union',
unionErrors: [Array],
path: [Array],
message: 'Invalid input'
}
],
format: [Function (anonymous)],
addIssue: [Function (anonymous)],
addIssues: [Function (anonymous)],
flatten: [Function (anonymous)]
}
I’ve read ERROR_HANDLING.md but I still struggle how to actually make a more proper error message (whithout a whole lot of ugly coding).
The error that I want to see is in the error message but hidden in the forest of other messages:
...
{
"code": "invalid_type",
"expected": "string",
"received": "undefined",
"path": [
"mytypes",
1,
"prop4"
],
"message": "Required"
}
...
What is the best strategy to handle this? Any tips/pointer appreciated.
I’m on Zod 3.11.6. My actual use case, a CLI app that parses a DSL yaml, has even more possible types in the union, so the error message is really long and hard to read.
Issue Analytics
- State:
- Created 2 years ago
- Reactions:1
- Comments:9 (2 by maintainers)
Top GitHub Comments
You could probably implement something like
z.discriminatedUnion
usingz.custom
that does something similar to these suggestions. I think it’s on the medium-term roadmap to support discriminated unions more directly since there is also a considerable speed increase with getting closer to constant-time evaluation here instead of linear.I implemented a function for handling discriminated unions (using zod 3.11.6).
zodDiscriminatedUnion(discriminator: string, types: ZodObject[])
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.Jest tests and usage (
zodDiscriminatedUnion.spec.ts
):Implementation (
zodDiscriminatedUnion.ts
):