Please consider using this helper:
const hasProperty = <Obj, Prop extends string>(obj: Obj, prop: Prop)
: obj is Obj & Record<Prop, unknown> =>
Object.prototype.hasOwnProperty.call(obj, prop);
in your case. in
operator works as expected mostly with unions. Please see here, here and here
Working solution:
const hasProperty = <Obj, Prop extends string>(obj: Obj, prop: Prop)
: obj is Obj & Record<Prop, unknown> =>
Object.prototype.hasOwnProperty.call(obj, prop);
const foo = (obj: unknown) => {
if (typeof obj === 'object' && obj) {
if (hasProperty(obj, 'foo') && typeof obj.foo === 'string') {
return obj.foo;
}
}
};
Playground
However, since you want to throw an error if obj
is invalid, you can use assert function
:
const hasProperty = <Obj, Prop extends string>(obj: Obj, prop: Prop)
: obj is Obj & Record<Prop, unknown> =>
Object.prototype.hasOwnProperty.call(obj, prop);
function foo(obj: unknown): asserts obj is { foo: string } {
const isValid =
typeof obj === 'object' &&
obj &&
hasProperty(obj, 'foo') &&
typeof obj.foo === 'string';
if (!isValid) {
throw new Error();
}
};
declare var obj: unknown;
foo(obj);
obj.foo // ok
Playground
obj
toany
before checking forfoo
– Seethe(obj: any) => {...
? – Globinunknown
?any
would work, but you've specifically mentionedunknown
so... – Auleaconst bar = obj as any
, you can access properties ofobj
. You need it to beany
to access its properties. You then checked'foo' in bar && typeof bar.foo === 'string'
, which fullfill your requirement. What else do you want? – Seetheunknown
doesn't add any additional type safety. You could just:const foo = (obj: any) => obj?.foo === 'string' ? obj?.foo : undefined
– Highmuckamuck