I'm trying to type guard an unknown type
const foo = (obj: unknown) => {
if (typeof obj === 'object' && obj) {
if ('foo' in obj && typeof obj.foo === 'string') {
return obj.foo;
}
}
};
Property 'foo' does not exist on type 'object'.
I also tried with is expression does not work:
const foo = (obj: unknown): obj is { foo: 'string' } => {
if (typeof obj === 'object' && obj) {
if ('foo' in obj && typeof obj.foo === 'string') {
return obj;
}
}
throw new Error();
};
CodePudding user response:
You're going to have to give TypeScript a little help here:
type fooObj = object & { foo: unknown };
const foo = (obj: unknown) => {
if (typeof obj === 'object' && obj) {
if ('foo' in obj && typeof (obj as fooObj).foo === 'string') {
return (obj as fooObj).foo;
}
}
};
CodePudding user response:
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;
}
}
};
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