I have a function which checks if an object is a "plain object" - that is, it's not an instance of any class, an object initialized like let x = { a: 1 }; I want to add a type guard to this function, to assert that the checked value matches an interface ([key: string]: any).
For some reason, the function does not narrow the type. Perhaps it is because "plain object" as I defined it and [key: string]: any are not the same thing at all (e.g. an instance of a class could also match [key: string]: any). Nevertheless, I want my isPlainObject() function to assert that if successful, the value matches [key: string]: any).
This is what I tried in the playground. I also included a "handbook" example to check for a specific property - which works.
interface IndexedObject {
[key: string]: any
}
interface FooObject {
foo: "bar"
}
// This works - an example from the handbook
const isFooObject = (value: any): value is FooObject => (
typeof value === 'object'
&& value !== null
&& typeof (value as FooObject).foo !== 'undefined'
);
/**
* Check if the value is a "plain" JavaScript object initialized
* with { } (not instance of any class)
*
* In addition to the runtime check, this function should assert that the
* argument matches IndexedObject interface
*
*/
const isPlainObject = (value: any): value is IndexedObject => (
typeof value === 'object'
&& value !== null
&& value.constructor === Object
&& Object.getPrototypeOf(value) === Object.prototype
);
function test<T>(value: T): T {
if (isFooObject(value)) {
const x = value; // T & FooObject - it works
}
if (isPlainObject(value)) {
const x = value; // T - doesn't work
}
return value;
}
Update1:
I think I'm not going to use a type guard here, as the IndexedObject is too broad and can produce unexpected results. This would tell TypeScript that otherwise the value never matches IndexedObject - which is not desirable. I will use inline assertions instead.
Unless it is possible to define a type that would match "plain object"?
Nevertheless, I'm still curious why the type guard doesn't work. Also, if I return the value inside if block, after the if block the type will be never - playground 2
any, which might not register as "meaningful". If you change the property type tonumberfor example, it will register. Btw, types like that can be written using the helperRecord(hereRecord<string, any>). - H.B.