DEV Community

Cover image for Exploring [key:string]: any in TypeScript
Liu Yongliang
Liu Yongliang

Posted on

Exploring [key:string]: any in TypeScript

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

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
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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:

interface StringArray {
    [index: number]: string;
}

const myArray: StringArray = getStringArray();
const secondItem = myArray[1]; // secondItem is of type string
Enter fullscreen mode Exit fullscreen mode

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:

interface PaintOptions {
  xPos: number;
  yPos: number;
}
Enter fullscreen mode Exit fullscreen mode

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:

interface NumberDictionary {
  [index: string]: number;
  length: number;
  width: number;
}
Enter fullscreen mode Exit fullscreen mode

So in the case of CustomState, both the following usage are correct:

const arrayExample:CustomState = {
  value: [{val: 1}]
}

const objectExample:CustomState = {
  value: {val: 1}
}
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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. Essentially, the person had an issue with Index signature of object type implicitly has an 'any' type. Scrolling further down, an proposed answer had something similar to my initial example:

type ISomeType = {[key: string]: any};

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

    let key: string = 'secondKey';

    let secondValue: string = someObject[key];
Enter fullscreen mode Exit fullscreen mode

In fact to add on, the declaration of ISomeType allows for the following to work as well:

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];
Enter fullscreen mode Exit fullscreen mode

But, if the use of any has been replaced, the whole thing would break:

// 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];
Enter fullscreen mode Exit fullscreen mode

Potential moral of the story? Don't use any 😂


Resources

The code snippets used in this article is also available at this TypeScript playground.

Top comments (3)

Collapse
 
arpitbhalla profile image
Arpit Bhalla

We can use Record instead of that.

Collapse
 
it718 profile image
IT Solutionist, Specialist

Thanks for your posting. I don't know about TypeScript. Is it necessary to learn in 2021?

Collapse
 
tlylt profile image
Liu Yongliang

I put off learning TypeScript until recently and I have to say so far the experience with TypeScript is amazing. Maybe because I prefer Java so it’s comforting to see all the type information.