BurnAsIce
BurnAsIce

Reputation: 45

A strange mismatch not noticed by Typescript. How is this possible?

Apparently, Typescript doesn't seem to recognize the difference between an object like {} and a generic array [] by accepting last one as input of a function that requires an object with {}'s structure.

To resume my problem, this is a simplified example to replicate it:

type test = { [key: string]: any };
let x: test = ["x", "y", "z"];

Actually, Typescript seems to accept this. How is this possible?

Note: The situation I ran into is more similar to this:

type fooType = { [key: string]: any };
const fooFunction<T extends fooType>(input: T) => // code...
fooFunction([]); // No red underline

But you can consider the first example. It's the same.

The main idea is to create a function that accepts only objects with a key (type string) and a value of any type.

Thank you in advance for the answers!

Upvotes: 1

Views: 99

Answers (1)

Adam Coster
Adam Coster

Reputation: 1462

Differentiating between plain objects and other things (like arrays, or even functions) can be frustrating in JavaScript (and therefore Typescript).

Since an array is an object, you need a type that excludes arrays. For completeness, you may also want to exclude other non-plain objects, like functions, dates, regexes, etc, but I'll just focus on arrays.

Using your example, here are some approaches:

1. Exclude objects with numeric indexes

function fooFunction<T extends {
  [key: string]: any,
  [index: number]: never
}>(input: T) { }
fooFunction(['']); // Will have red underline!
fooFunction([]); // This will NOT have an underline!

In the above case, we're saying that T cannot have any numeric indexes. There is an edge case, though: an empty array has type never[], which also has no numeric indexes!

2. Exclude array-specific fields

Another approach is to identify some property common to arrays that won't be in any of the objects you plan to pass through your function:

function fooFunction<T extends {
  map?: never,
}>(input: T) { }
fooFunction(['']); // Will have red underline!
fooFunction([]); // So will this!

3. Narrow the parameter type

The cleanest approach is to narrow your generic at the parameter to exclude arrays. The following example uses a utility type that returns never for lots of non-plain-object inputs (but not all of them):

type FancyObject = any[]|Function|Date|RegExp|Error
type PlainObject<T> = T extends FancyObject
  ? never
  : T extends { [key: string]: any }
  ? T
  : never;
  
function fooFunction<T>(input: PlainObject<T>) {}
fooFunction(['']); // Will have red underline!
fooFunction([]); // So will this!
fooFunction({ hello: 'world' }) // This is fine!

Upvotes: 1

Related Questions