Easiest way
Make a generic hasProp typeguard
const hasProp = <T extends string>(obj: object, prop: T): obj is { [K in T]: any } => {
return prop in obj;
}
const someFunc = (obj: object) => {
// obj is of type object and we have no clue if he has 'foo' prop or not
if (hasProp(obj, 'foo')) {
// obj is of type { foo: any }, so we can use foo now
const some = obj.foo;
}
}
you will have what you need, but it will break type of obj, it is not a solution if you want to use some props of obj other than foo in the if block.
A little bit better
Add second generic to describe object type and keep it as is
const hasProp = <O extends object, T extends string>(obj: O, prop: T): obj is O & { [K in T]: any } => {
return prop in obj;
}
const someFunc = (obj: { bar: number }) => {
// obj is of type { bar: number } and 'foo' prop is not here
if (hasProp(obj, 'foo')) {
// obj is of type { bar: number } & { foo: any }
// so we managed to persist previous obj type and add 'foo' after typeguard use
const fooProp = obj.foo;
const barProp = obj.bar;
}
}
We did not break obj type and managed to add foo prop, but still, foo type is any. And probably we want to persist type of foo prop too, so we need to infer its type from obj.
Best solution
We first define a type for ObjectWithProp, what keeps a type of the object, if it already has foo prop, and adds it as any if not
type ObjectWithProp<O extends object, P extends string> = P extends keyof O
? O
: O & { [K in P]: any };
Then we just use it in the typeguard and have desired result
const hasProp = <O extends object, P extends string>(obj: O, prop: P): obj is ObjectWithProp<O, P> => {
return prop in obj;
}
const someFunc = (obj: { bar: number, foo: string }) => {
if (hasProp(obj, 'foo')) {
// obj kept it's type and we can still see that foo is a string
const some = obj.foo;
}
}
const someFunc2 = (obj: { bar: number }) => {
if (hasProp(obj, 'foo')) {
// if obj has no 'foo' prop initially, then it will be added as `any`
const some = obj.foo;
}
}
Playground link