In other words I need a type which represents exactly the following conditions:
batchObj !== null &&
typeof batchObj === "object" &&
Array.isArray(batchObj) === false
I used to use this type predicate function until I noticed that { [key: string]: any }
includes arrays:
function isObject(obj: unknown): obj is { [key: string]: any } {
return (
obj !== null && typeof obj === "object" && Array.isArray(obj) === false
);
}
This question is really harder to answer than I thought.
TypeScript does not have true negated types like not X
meaning "anything except X
". There is an experimental pull request at microsoft/TypeScript#29317 where this is implemented, but it has not been and might never be merged into the main language. So there's no specific type that maps completely to object & not Array<any>
.
Instead there are various types of workarounds. The simplest one I can think of is to come up with a specific type to which arrays are not assignable, but to which most other object types are assignable. For example:
type NonArrayObject = object & { [k: string]: any; forEach?: undefined }
Here we are saying that a NonArrayObject
must be a non-primitive ( object
), and which can any value for any string
key, except that the property named forEach
must either me missing or undefined
. This is pretty close to what you're looking for:
let nonArrayObject: NonArrayObject;
nonArrayObject = null; // error
nonArrayObject = "oops"; // error
nonArrayObject = {}; // okay
nonArrayObject = { a: 1, b: 2, c: "" }; // okay
nonArrayObject = [1, 2, 3]; // error
Of course, as a workaround, it's not perfect. Maybe you really want to allow a forEach
property?
nonArrayObject = { forEach: 123 }; // error!
Well, arrays have all sorts of methods and properties and maybe you can find one that you'd rather declare off limits. Maybe push
?
type NonArrayObject = object & { [k: string]: any; push?: undefined }
Or lastIndexOf
?
type NonArrayObject = object & { [k: string]: any; lastIndexOf?: undefined }
No? you need to allow every possible key? What if we allow forEach
but require that it be a non-function (noting that not Function
also doesn't exist)?
type NonArrayObject = object & {
[k: string]: any;
forEach?: undefined | string | number | boolean | null | { call?: never }
}
Et cetera, et cetera. Pragmatically speaking, as long as you can identify some concrete "non-arraylike object" type, you can use it. Personally I have never needed a non-array object with a forEach
property so the original workaround would be the way I'd proceed.
This at least works for the true
case of your type guard function:
declare function isObject(obj: unknown): obj is NonArrayObject;
const obj = Math.random() < 0.5 ? { a: 1, b: 2, c: 3 } : [1, 2, 3];
if (isObject(obj)) {
console.log(obj.b.toFixed(2)); // okay
}
A different workaround is to give up on specific types and instead use a generic type. For example, you could use the following generic isObject
definition which will behave as you expect, I think:
function isObject2<T>(obj: T | any[]): obj is T {
return (
obj !== null && typeof obj === "object" && Array.isArray(obj) === false
);
}
if (isObject2(obj)) {
console.log(obj.b.toFixed(2));
} else {
console.log(obj.join(","));
}
That uses an inference from the union type T | any[]
T | any[]
to T
to implicitly remove any[]
from the domain of the type. Similarly:
function isObject3<T>(obj: T): obj is Exclude<T, any[]> {
return (
obj !== null && typeof obj === "object" && Array.isArray(obj) === false
);
}
if (isObject3(obj)) {
console.log(obj.b.toFixed(2));
} else {
console.log(obj.join(","));
}
uses the Exclude
utility type to explicitly remove any[]
from the domain of the type.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.