Home > Software engineering >  why doesn't typescript accept if(Array.length) for differentiating between an object and array
why doesn't typescript accept if(Array.length) for differentiating between an object and array

Time:09-28

I recently started using typescript and I've run into a confusing little part, where I don't quite get why typescript behaves like it does. (Involving typechecking a parameter that could either be an array or an object)

I first tried checking as the upper example shows, but it didn't work (Typescript didn't like me calling .map

Error Message: This expression is not callable. Not all constituents of type 'string | ((callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[])' are callable. Type 'string' has no call signatures.ts(2349)

I figured it had something to do with calling map on objects so I replaced the condition with Array.isArray() - and suddenly typescript was happy. - But why wasn't it before? - because in my mind objects wouldn't be able to pass regardless.

interface Object {
  [key: string]: string
}

function myFunction(a: string[] | Object) {
  if(a.length) {
    a.map(x => x)
  }
}

if I change the condition from a.length to Array.isArray(a) typescript realizes that objects won't pass this check - but why didn't it before? (since a.length will return undefined if called on an object - so the check would always fail if a was an object)

So I guess my question is: Why does typescript only accept Array.isArray() as a valid check in this case? - Or is there something else going on I am not aware of?

interface Object {
  [key: string]: string
}

function myFunction(a: string[] | Object) {
  if(Array.isArray(a)) {
    a.map(x => x)
  }
}

CodePudding user response:

Consider the following code:

interface Object {
  [key: string]: string
}

function myFunction(a: string[] | Object) {
  if(a.length) {
    console.log(`I think this is an array: ${a}; it's type is ${typeof a} and it ${Array.isArray(a) ? "is" : "isn't"} an array`);
  } else {
    console.log(`I think this is not an array: ${a}; it's type is ${typeof a} and it ${Array.isArray(a) ? "is" : "isn't"} an array`);
  }
}

myFunction(["a"]);
myFunction({ length: "hello" });

link here

Any object can have the length property, not just arrays. If you want to go down the route of checking for a property, you could just check those you want to use:

interface Object {
  [key: string]: string
}

function myFunction(a: string[] | Object) {
  if(a.hasOwnProperty("map") && typeof a.map === "function") {
    const b = a.map(x => x);
  }
}

CodePudding user response:

Your problem is an incorrect assumption:

since a.length will return undefined if called on an object - so the check would always fail if a was an object

Because you defined Object with [key: string]: string, it could very well have a length property, though it would be a string.

You can examine the following proof in Typescript Playground:

interface Object {
  [key: string]: string
}


function getLength(o: Object) {
    return o.length
}

const obj: Object = {length: 'long'}

const x = getLength(obj)  // x is of type string

const objTwo: Object = {}

const x2 = getLength(obj)  // x2 is still of type string

The only failure I see in Typescript's type inference is that the return type of getLength should be string | undefined. I'm not sure why Typescript fails to figure this out.

Nonetheless, the fact that an Object instance could have a length property means your logic is incorrect.

  • Related