TL;DR
type IsKeyOptional<T extends Record<string | number | symbol, unknown>, Keys extends keyof T> =
{[Key in Keys]?: T[Key]} extends Pick<T, Keys> ? true : false;
type Foo = {
foo?: string
bar: string | undefined // this is not the same as foo!
}
type IsFooOptional = IsKeyOptional<Foo, "foo"> // true
type IsBarOptional = IsKeyOptional<Foo, "bar"> // false
The problem
Recently I've run into a problem with using zod where zod deliberatly adds | undefined
to the value of a key to then add the optional flag ?
depending on whether or not the value includes undefined
.
This check isn't accurate though as there is a fundamental difference between optional keys and nullable values, which is the exact reason why exactOptionalPropertyTypes
exists. "IF you give me a banana, I expect it to be a banana, not a banana | undefined
"
Correct checking
Think of objects as rows of [key, value]
tuples for a second and the solution becomes clearer. In order to determine whether a key is optional we have to check whether the respective row is present, not whether the value includes undefined
. And with that in mind we can just say
for a set of keys construct rows that are optional and check whether those rows are equal to the rows for the same keys from the type we want to check against
Or, more picturesque:
isOptional | check | control |
---|---|---|
false | maybe the row exists | the row DOES exist (even if its value can be undefined) |
true | maybe the row exists | maybe the row exists |
Top comments (0)