I’m trying to perform a type check on an optional property to determine whether it is undefined when no value is assigned. My goal is to dynamically check if an optional property has been set or not, since I want to handle cases where the property is intentionally left out.
type A = {
aa?: string
}
const a:A = {}
const { aa } = a
type B = typeof aa extends undefined ? true : false // false
Since I didn’t assign a value to aa
, I expected typeof aa
to be undefined
, and B
to be true
. However, the result is false
.
How can I check the type dynamically when no value is assigned?
4
When you annotate a variable with a non-union type like A
:
const a: A = {}
you are throwing away any information TypeScript might have had about the initializing value {}
. All TypeScript knows about a
is that its type is A
, and thus the aa
property is of type string | undefined
. And since (string | undefined) extends undefined
is false, you get the behavior you’re seeing:
const { aa } = a;
type B = typeof aa extends undefined ? true : false // false
In other words, TypeScript does not narrow a
from A
to something else upon that assignment. If you could make A
a union like
type A = { aa: string } | { aa?: undefined }
then TypeScript will perform assignment narrowing at
const a: A = {}
which can be seen by observing a
afterwards:
const { aa } = a;
// ^? const a: { aa?: undefined; }
And thus aa
is of type undefined
as desired.
const a: A = {}
const { aa } = a
type B = typeof aa extends undefined ? true : false // true
Of course {aa?: string}
and {aa: string} | {aa?: undefined}
aren’t identical to each other. They behave mostly the same if you’re just reading from the variable, but when setting the aa
property you can’t switch from one union member to the other:
let a: A = {}; // okay either way
a.aa = "abc"; // not okay with union type
a = { aa: "abc" }; // okay either way
delete a.aa; // not okay with union type
So if you can’t make that change then you’re kind of stuck. There is a longstanding open feature request at microsoft/TypeScript#16976 to implement non-union assignment narrowing, but it’s not part of the language now and I wouldn’t expect to see it anytime soon.
Playground link to code