-
-
Notifications
You must be signed in to change notification settings - Fork 304
Description
Discussed in #1366
Originally posted by mrft December 2, 2025
I'll start with a simple example:
const SimpleSchema = V.object({ x: V.number() });
type TSimpleInput = V.InferInput<typeof SimpleSchema>; // { x: number; } but in practice { x: number; [key: string]: any; }
type TSimpleOutput = V.InferOutput<typeof SimpleSchema>; // { x: number; }
console.debug(
"simple object parse result:",
V.safeParse(SimpleSchema, { x: 5, y: 7 })
);So the inferred input type is stricter than what safeParse will allow.
This bug (in my opinion) is also reflected in the intersection docs: https://valibot.dev/guides/intersections/, which further suggests that the implementation is not in line with people's intuition about how it should work:
These docs use the following example:
// TypeScript
type Intersect = { foo: string } & { bar: number };
// Valibot
const IntersectSchema = v.intersect([
v.object({ foo: v.string() }),
v.object({ bar: v.number() }),
]);but in reality we get:
// TypeScript
type Intersect = { foo: string; [key: string]: any; } & { bar: number; [key: string]: any; };
// Valibot
const IntersectSchema = v.intersect([
v.object({ foo: v.string() }),
v.object({ bar: v.number(); }),
]);because when parsing, it will not complain about extra properties, whereas the typescript compiler would complain about extra properties in the first version.
This gets worse when using a strictObject:
const firstSchema = v.strictObject({
a: v.number(),
b: v.string(),
});
type TFirstInput = v.InferInput<typeof firstSchema>; // { a: number; b: string }
const secondSchema = v.strictObject({
c: v.number(),
});
type TSecondInput = v.InferInput<typeof secondSchema>; // { c: number; }
const intersectSchema = v.intersect([firstSchema, secondSchema]);
type TIntersectInput = v.InferInput<typeof intersectSchema>; // { a: number; b: string; c: number; }
console.debug(
"intersectSchema parse result:",
v.safeParse(intersectSchema, { a: 1, b: "foo", c: 3 })
); // => {
// typed: false,
// success: false,
// output: { a: 1, b: "foo", c: 3 },
// issues: [
// 'Invalid key: Expected never but received "c"',
// 'Invalid key: Expected never but received "a"',
// ] This is not inline with the inferred types, but also unintuitive (as well as useless) from a user perspective.
(Unfortunately zod has the exact same bug)
REMARK: if this is how it is supposed to work, then toJsonSchema should also be updated to avoid simply producing { allOf: [ { additionalProperties: false }, { ... } ] } as that would have the same problem (it should be { type: "object", unevaluatedProperties: false, allOf [ { ... additionalProperties undefined }, { ... additionalProperties undefined } ] } without the additionalProperties key, not supported in JSON-Schema draft‑07 but introduced since the JSON Schema 2019‑09 specs).
I let Github Copilot crunch on the issue to make it behave the way I would expect it to behave: https://github.com/mrft/valibot/tree/feat-intersect-strict