Exploring [key:string]: any in TypeScript
Tags: typescript, computerscience
Published: 2021-05-29 02:31:41


With this series I intend to note down some of the confusion and quirky stuff that I encountered out in the wild. So, today I am going to start with this snippet in TypeScript. 
## Motivation
```typescript
interface CustomState {
  value: {
    [key:string]: any
  }
}

const defaultState : CustomState = {
  value: {}
}

const reducer = (state: CustomState, action: { type: string }): CustomState => {
  if (action.type === 'reset') {
    return {
      value: []
    }
  } else {
    return {
      ...state
    }
  }
}
```
The `CustomState` declared at the start includes a property called `value`, which is an object with key-value pairs of the form `string - any`. The `defaultState` variable contains an (empty) object conforming to the interface declared above, which is perfectly normal.

The thing that caught me off-guard is in the reducer. The reducer function is supposed to reset the state by clearing out the `value` property. However, notice here that an array `[]` is used, instead of `{}`. 

I thought the change from type `object` to type `array` is pretty drastic, especially if I compare it to Java (Changing from a `HashMap` to an `ArrayList` just like that? Is this even allowed?). The strangest part of this was that TypeScript had no qualms about this at all. No curly lines nor compiler warnings.

---

## Exploration
The first thing I did was to find out whether the interface was declared correctly, i.e. is it declaring that `value` contains an object or an array. This led me to review the definition of index signature. 
#### Index Signature
Index signature is a way to describe the types of possible values.  Borrowing the examples used in  the official TypeScript docs:
```typescript
interface StringArray {
	[index: number]: string;
}

const myArray: StringArray = getStringArray();
const secondItem = myArray[1]; // secondItem is of type string
```

The syntax to declare the index signature might seem strange at first. It looks like declaring an object with `{}` but in the above example, it is used for declaring an interface for an array. For comparison, the way to declare an object interface looks like this:
```typescript
interface PaintOptions {
  xPos: number;
  yPos: number;
}
```

In the TypeScript documentation example, index signature is used to describe an array. However, there is also a line below that says:
>While string index signatures are a powerful way to describe the “dictionary” pattern, they also enforce that all properties match their return type.

Doing a bit more research would point me to other examples of how index signatures are also applicable to objects:
```typescript
interface NumberDictionary {
  [index: string]: number;
  length: number;
  width: number;
}
```
So in the case of `CustomState`, both the following usage are correct:
```typescript
const arrayExample:CustomState = {
  value: [{val: 1}]
}

const objectExample:CustomState = {
  value: {val: 1}
}
```

---
#### Array VS Object
The second thing I checked was that since `{}` could be replaced with `[]`,  are arrays and objects, besides what we already know about the different use cases, the same thing in JavaScript/TypeScript? Without going too deep into this question, we can make an observation with console log :
```
console.log(typeof []) // "object"
console.log(typeof {}) // "object"
```

---
#### Stack Overflow?
The last bit of things of interest came up when I started to draft examples for this article and encountered this stack overflow [question](https://stackoverflow.com/questions/32968332/how-do-i-prevent-the-error-index-signature-of-object-type-implicitly-has-an-an). Essentially, the person had an issue with `Index signature of object type implicitly has an 'any' type`.  Scrolling further down, an proposed [answer](https://stackoverflow.com/a/58347263/13725861) had something similar to my initial example:
```typescript
type ISomeType = {[key: string]: any};

    let someObject: ISomeType = {
        firstKey:   'firstValue',
        secondKey:  'secondValue',
        thirdKey:   'thirdValue'
    };

    let key: string = 'secondKey';

    let secondValue: string = someObject[key];
```
In fact to add on, the declaration of `ISomeType`  allows for the following to work as well:
```typescript
type ISomeType = {[key: string]: any};

// My additional example
let someArray: ISomeType = [
    {firstKey:   'firstValue'},
    {secondKey:  'secondValue'},
    {thirdKey:   'thirdValue'}
]

let newkey: string = 'secondKey';

let newSecondValue: string = someArray[newkey];
```
But, if the use of `any` has been replaced, the whole thing would break:
```typescript
// Note the change in the type and therefore the error!
type ISomeTypeA = {[key: string]: string};

let someObjectA: ISomeTypeA = {
    firstKey:   'firstValue',
    secondKey:  'secondValue',
    thirdKey:   'thirdValue'
};

let keyA: string = 'secondKey';

let secondValueA: string = someObjectA[keyA];

// My additional example
let someArrayA: ISomeTypeA = [  // Error: Type '{ firstKey: string; }' is not 
    {firstKey:   'firstValue'}, // assignable to type 'string'.
    {secondKey:  'secondValue'},
    {thirdKey:   'thirdValue'}
]

let newkeyA: string = 'secondKey';

let newSecondValueA: string = someArrayA[newkeyA];
```

Potential moral of the story? Don't use `any` :joy:

---
## Resources
The code snippets used in this article is also available at this [TypeScript playground](https://tsplay.dev/w26nVw).