zod
zod copied to clipboard
Bug: .merge changes schema to disallow extra props when importing commonJs build
The following error occurs when the parser encounters an unexpected property:
Expected never, received <type - array/string/etc.>
.
In other words, it's as if it has .strict()
, even though that was never applied.
This happens in the following scenario:
- importing a schema from a commonJs file AND
- performing a
.merge
with that schema THEN - with the new schema, parsing an object that has extra (unexpected) properties.
This issue can best be explained / understood by looking at the CodeSandbox. The CodeSandbox runs the tests to show which case fails. CodeSandbox: https://codesandbox.io/p/devbox/zod-vite-bug-y2qsc8?file=%2Fsrc%2Ftest.spec.ts
CodeSandbox test file:
// baseSchema is a basic `z.object({ property1: z.string() })` that has been compiled to cjs and esm
// using tsup. This build occurs in the CodeSandbox.
import { baseSchema as baseSchemaCjs } from "../external_package_mock_dist/index.cjs";
import { baseSchema as baseSchemaEsm } from "../external_package_mock_dist/index.js";
import { z } from "zod";
it("imported cjs zod schema parse - no extra properties (PASS)", () => {
expect(() =>
baseSchemaCjs.parse({
property1: "1",
}),
).not.toThrow();
});
it("imported cjs zod schema parse - extra properties (PASS)", () => {
expect(() =>
baseSchemaCjs.parse({
property1: "1",
extraProperty: "str",
}),
).not.toThrow();
});
it("imported cjs zod schema merged parse - extra properties (FAIL)", () => {
const schema = baseSchemaCjs.merge(
z.object({
property2: z.string(),
}),
);
expect(() =>
schema.parse({
property1: "str",
property2: "str",
extraProperty: "str",
}),
).not.toThrow();
});
it("imported esm zod schema merged parse - extra properties (PASS)", () => {
const schema = baseSchemaEsm.merge(
z.object({
property2: z.string(),
}),
);
expect(() =>
schema.parse({
property1: "str",
property2: "str",
extraProperty: "str",
}),
).not.toThrow();
});
it("locally created zod schema merged parse - extra properties (PASS)", () => {
const baseSchemaLocal = z.object({
property1: z.string(),
});
const schema = baseSchemaLocal.merge(
z.object({
property2: z.string(),
}),
);
expect(() =>
schema.parse({
property1: "str",
property2: "str",
extraProperty: "str",
}),
).not.toThrow();
});
Expected result
According to the docs, By default Zod object schema strip unknown keys from the output.
Note: adding this to the Vite config fixes the issue:
optimizeDeps: {
exclude: ['zod'],
},
test: {
alias: {
zod: path.resolve('./node_modules/zod/lib/index.js'),
},
}
But hoping the issue can be solved at its core.
UPDATE: No longer think that this issue is only in Vite. Updated the Codesandbox-- am seeing the issue even when not using Vitest.
Possibly related: https://github.com/colinhacks/zod/issues/222
I am experiencing the same issue in a react cra app:
// Myform.rules.ts
import { mySchema } from "../../mySchema"
import { zodResolver } from "@mantine/form";
import { z } from "zod";
const myFormSchema = mySchema.merge(
z.object({
check: z.boolean().optional(),
})
);
export type MyValues = z.infer<typeof myFormSchema>;
export const rules = zodResolver(myFormSchema);
// MyForm.tsx
...
const form = useForm<MyValues>({
initialValues: myItem
? {...myItem}
: undefined,
validate: rules,
});
This results in form not being valid: id: 'Expected never, received string'
Code with same structure but no merge works fine
~! The issue does not appear when using extend
instead of merge
+1 same here. Receiving expected "never", received "object"
at runtime when using a bundled version of my app built with esbuild
and running on Node 20 in a Lambda environment. Before bundling node_modules in my app it worked well
I’m also having this issue with vitest
for my tests. No problem using tsx
for starting the app. Only the tests are failing.