Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to add index signature in TypeScript

I am trying to use reduce with Typescript to reach a total count of incoming messages. I'm confused on how to add an index signature. I keep getting the error: " Element implicitly has an 'any' type because type '{}' has no index signature." on variables newArray and counts[k]. I have read through several similar questions but have not figured out how to apply them to my specific scenario. I am new to JavaScript and TypeScript.

Here is my array:

        var data = [
        { time: '9:54' }, 
        { time: '9:54' },
        { time: '9:54' },
        { time: '9:55' }, 
        { time: '9:55' },
        { time: '9:55' },
        { time: '9:55' },
        { time: '9:56' }, 
        { time: '9:56' },
        { time: '9:56' },
        { time: '9:56' },
        { time: '9:57' }, 
        { time: '9:57' },
        { time: '9:57' },
        { time: '9:57' },
        { time: '9:57' }
    ];

This is how I need my array for use in a rechart graph:

        var dataWithCounts = [
        { time: '9:54', messages: 3 }, 
        { time: '9:55', messages: 4 }, 
        { time: '9:56', messages: 4 }, 
        { time: '9:57', messages: 5 }
    ];

With the help of 'Just a Student's' answer from this Stack Overflow question: Group and count values in an array , I have the following.

        var counts = data.reduce((newArray, item) => {
           let time = item.time;
           if (!newArray.hasOwnProperty(time)) {
               newArray[time] = 0;
           } 
           newArray[time]++;
           return newArray;
        }, {});

        console.log(counts);

        var countsExtended = Object.keys(counts).map(k => {
           return { time: k, messages: counts[k] };
       });

       console.log(countsExtended);

Where and how do I declare an index signature? Below are various things I've tried.

  • let newArray: { [time: string] }; and receive a duplicate identifier error.

  • adding string to the parameter var counts = data.reduce((newA:string, item) gives me an error "Element implicitly has an 'any' type because index expression is not of type 'number'."

  • adding newA[time].toString()gives me errors, "The left-hand side of an assignment expression must be a variable or a property access."

like image 580
Bethany Avatar asked Feb 13 '18 18:02

Bethany


3 Answers

The type of your accumulator in the .reduce call is almost certainly the issue. Since it is just given as {}, that's what its type is inferred as, and the type {} doesn't have an index signature. You can fix this by casting your initial value so that it does include a signature:

var counts = data.reduce((newArray, item) => {
    let time = item.time;
    if (!newArray.hasOwnProperty(time)) {
        newArray[time] = 0;
    } 
    newArray[time]++;
    return newArray;
}, {} as {[key: string]: any}); // <-- note the cast here

I gave the index signature type any, but you may want to make it something more specific for your particular case.

like image 160
CRice Avatar answered Oct 17 '22 15:10

CRice


Answer to Original Question

The main caveat was that the reduce accumulator value was not properly typed.

Original code:

const result = data.reduce(someLogicCallback, {})

Need to do in the following way:

const accumulator: {[key: string]: number} = {}
const result = data.reduce(someLogicCallback, accumulator)

Other Possible Caveats

This part might be helpful for the later occasional googlers.

I came up here looking to solve a similar issue, but in my case I had an object with an already predefined type

typeof someObject // predefinedType

type predefinedType = {
  someKey: number;
  otherKey: number;
}

and I got index signature is missing in type predefinedType error when I tried to extract entries from that object

const entries = Object.entries<number>(someObject);

The obvious solution was to do the type assertion

type TypedIndexObj = {[key: string]: number}
const entries = Object.entries<number>(someObject as TypedIndexObj);

But this would be too restrictive because it will always assert values types of someObject as number which might change over time.

The best way of adding index type of such an object that I came up with is to use keyof syntax

type TypedIndexObj = {[key in keyof typeof someObject]:(typeof someObject)[key]}
const entries = Object.entries<number>(someObject as TypedIndexObj);

Reference: Corrected Code from the Original Question

Here is the fully typed corrected version of the code from the original question:

const accumulator: {[key: string]: number} = {}

const counts = data.reduce((_accumulator, item) => {
  const time = item.time;
  // no error on the next line
  if (!_accumulator.hasOwnProperty(time)) {
      _accumulator[time] = 0;
  } 
  _accumulator[time]++;
  return _accumulator;
}, accumulator);

const countsExtended = Object.keys(counts).map(k => {
    return { time: k, messages: counts[k] };
});
like image 32
Kirill Taletski Avatar answered Oct 17 '22 15:10

Kirill Taletski


The correct types for your array would be:

Array<{time: string}>

or:

{time: string}[]

or:

{[key: number]: {time: string}}
like image 5
Jonas Wilms Avatar answered Oct 17 '22 16:10

Jonas Wilms