valibot
valibot copied to clipboard
Cannot refine instance properties using `intersect` of `instance` and `object`
In many validation libraries, I can refine instance properties by using the intersection (also called conjunction) of a class instance and an object schema. Valibot doens't support this.
import * as v from "valibot";
class Box<A> {
constructor(public readonly content: A) {}
}
const Simple = v.intersect([
v.instance(Box<number>),
v.object({ content: v.number() }),
]);
console.log(v.is(Simple, new Box("box"))); // false
console.log(v.is(Simple, new Box(42))); // false
Hi @chengluyu! I'm Dosu and I’m helping the valibot team.
Valibot doesn't support refining instance properties by intersecting v.instance and v.object schemas. This is because Valibot treats class instances and plain objects as distinct types—when you use v.intersect with v.instance(Box) and v.object({ content: v.number() }), the object schema's validation isn't applied to class instances, even if the instance has matching properties. This is a deliberate design choice for strict type separation, which differs from some other validation libraries that allow this pattern. You can see this discussed in issue #1227.
As a workaround, maintainers suggest using v.looseObject and extending its pipe, or restructuring your validation logic, but direct intersection of instance and object schemas for property refinement isn't currently supported. More details on intersections and their limitations are in the official guide.
To reply, just mention @dosu.
How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other
[...] because Valibot treats class instances and plain objects as distinct types [...]
That doesn't sound reasonable, because checking Box alone using the object schema passes without any issues.
const Simpler = v.object({ content: v.number() });
console.log(v.is(Simpler, new Box("box"))); // false
console.log(v.is(Simpler, new Box(42))); // true
Hey, I guess the problem is that a plain object will fail the validation of v.instance(Box<number>). What do you expect as an output in this case? A plain object with properties from Box and the object schema? Or are you looking for an OR condition. If so you can replace v.intersect(...) with v.union(...). Here more context: https://valibot.dev/guides/unions/
I guess the problem is that a plain object will fail the validation of
v.instance(Box<number>)
I haven't seen the author put a plain object as input for v.is.
What do you expect as an output in this case?
I guess the author expected the first comment's second false to be true.
I don't want union in my case.
const phoneNumberErrorSchema = v.intersect([
v.instance(ClientError),
v.object({
body: v.object({
phoneNumber: v.pipe(v.array(v.string()), v.minLength(1)),
}),
}),
])
I traced library code execution and found the following.
if (value1 && value2 && value1.constructor === Object && value2.constructor === Object) {
I used ClientError type, so it failed to merge. I can't sure this is intended.
Hey, I guess the problem is that a plain object will fail the validation of
v.instance(Box<number>). What do you expect as an output in this case? A plain object with properties fromBoxand the object schema?
No, I’m not passing plain objects to v.instance(Box<number>).
Or are you looking for an OR condition. If so you can replace
v.intersect(...)withv.union(...). Here more context: https://valibot.dev/guides/unions/
No, OR matches either an instance of Box or an object with the property content being a number.
I want to create an validation object matching an instance of Box with the property content being a number.
From the perspective of set theory, I want to match the intersection of the set of all Boxes and all objects having the property content set to any number.
The intersect schema attempts to merge the output of both schemas, so it cannot be used in this case. I recommend using a pipe to check the additional properties of a specific class.
import * as v from "valibot";
class Box<A> {
constructor(public readonly content: A) {}
}
const Schema = v.pipe(
v.instance(Box<number>),
v.check((input) => typeof input.content === 'number')
);