I want to parse an array of different schemas that depend on a base type :
// Simple example
const typeA = z.object({ prop: z.string() });
const typeB = typeA.extend({
extraProp: z.boolean(),
});
I tried with the following schema :
const schema = z.array(z.union([typeA, typeB]));
The thing is when I parse the data, the typeB
items get parsed as type A
, so the extraProp
is removed from the output
const data = schema.parse([{ prop: 'foo', extraProp: true }]);
// => [{"prop":"foo"}]
When I put typeB
as the first item of the union, it seems to output the right type, but when wrong data is sent, it is parsed to typeA
, :
const schema = z.array(z.union([typeB, typeA]));
const data = schema.parse([{ prop: 'foo', extraProp: true }]);
// => [{"prop":"foo","extraProp":true}]
...
const data = schema.parse([{ prop: 'foo', extraProp: 4 }]);
// => [{"prop":"foo"}] - should fail here
Is my schema wrong here or is it something that zod is not capable of?
From zod's Unions section (emphasis added):
Zod will test the input against each of the "options" in order and return the first value that validates successfully.
Nothing more and nothing less: in z.union([typeA, typeB])
it tries
typeA
and if it validates, returns the parsed data, if not, it would
try typeB
, and so on.
One tends to think that it computes some form of a united object schema,
but it doesn't. Which explains why the last example validates: typeB
fails,
then it just parses typeA
, like there's no typeB
.
And one is right to expect that - it doesn't validate, for instance, in TypeScript:
type A = {prop: string};
type B = A & {extraProp: boolean};
type Sch = B | A;
const o: Sch = { prop: 'foo', extraProp: 2 };
// error TS2322: type 'number' is not assignable to type 'boolean'
The construct that would emulate the expected behavior in both case is
.merge
:
const typeA = z.object({ prop: z.string() });
const typeB = typeA.extend({
extraProp: z.boolean(),
});
const schema = z.array(typeB.merge(typeA));
const data = schema.parse([{ prop: 'foo', extraProp: 2 }]);
//{prop: 'foo', extraProp: true}
// ......
const schemaBA = z.array(typeB.merge(typeA));
schemaBA.parse([{ prop: 'foo', extraProp: 2 }]);
//Uncaught ZodError: [{
// //......
// "message": "Expected boolean, received number"
// }]