Should we change the object schema?
Great news! Valibot v1 RC is just around the corner. There are only a few issues holding us back. One of them is issue #983, which may lead to a breaking change. That's why I want to discuss this with you before making a final decision. This blog post will explain the current situation and the reasons for the proposed changes.
The problem
Currently, Valibot does not distinguish between missing and undefined object entries. This leads to a mismatch between the input and output values of a schema and their types when TypeScript's exactOptionalPropertyTypes
configuration is enabled.
import * as v from 'valibot';
// This throws no error and types `output` as `{ key?: string }`
const Schema = v.object({ key: v.optional(v.string()) });
const output = v.parse(Schema, { key: undefined });
// TypeScript thinks that `key` is a string if it is present
// but this is wrong because `key` is actually `undefined`
if ('key' in output) {
const key = output.key;
}
The solution
The solution to this mismatch is to finalize PR #1013 and change the implementation of object
and optional
to distinguish between missing and undefined object entries. To allow missing entries, optional
must be used as the outermost schema of an object entry. To explicitly allow undefined
as a value, another schema function called undefinedable
must be used.
const Schema = v.object({
key1: v.string(), // key1: string
key2: v.optional(v.string()), // key2?: string
key3: v.undefinedable(v.string()), // key3: string | undefined
key4: v.optional(v.undefinedable(v.string())), // key4?: string | undefined
});
The impact
As seen in the previous code example, defining an object entry that can be both missing and undefined gets a little verbose. This gets especially ugly if you define a default value for both cases.
const Schema = v.object({
key: v.optional(
v.undefinedable(v.string(), 'undefinedable_default'),
'optional_default'
),
});
To fix this, we could add a new function that covers both. The problem is that there is a third schema function called nullable
that also allows null
values. So adding a new function to cover every possible combination of optional
, undefinedable
and nullable
would result in 7 functions in total. I doubt this will make the API any better.
Let's discuss
What do you think? Should we distinguish between missing and undefined object entries and fully support TypeScript's exactOptionalPropertyTypes
configuration? If so, should we remove nullish
and only provide 3 strong primitives with optional
, undefinedable
and nullable
? Or should we add 4 more functions to cover all possible combinations? If so, what names would you use?
Your opinion matters to me. I encourage everyone to share their thoughts. Even quick feedback like "I like this ... and I don't like that ..." is welcome and will help to shape Valibot's future API design. Please discuss with us on GitHub or share your thoughts on social media.