简体   繁体   中英

Is there a type which represents an object with any property but is no array

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.


Playground link to code

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM