Skip to content
Advertisement

Getting error while referencing the type as index

I have an array of objects with the following structure

let sampleData = [
    { valueObj: { High: 4, Low: 5,  Medium: 7 } , time: "1571372233234" , sum: 16 },
    { valueObj: { High: 5, Low: 3, Medium : 1 }, time: "1571372233234" , sum: 9},
    { time: "14354545454", sum: 0},
    { time: "14354545454", sum: 0} }
];

I need to take each key within each object inside array and form an array out of it. Basically grouping based on the key present in all the objects.If the object has no “values” it should return 0 across the val1,val2,val3.

result = [
  { name: 'High', data: [4, 5, 0, 0] }, 
  { name: 'Medium', data: [5, 3, 0, 0] }, 
  { name: 'Low', data: [7, 1, 0, 0] }
];

Here I am passing an string, that will be used inside reduce.I am getting this error any cannot be used as index whenever I pass in a dynamically string that will be used inside a reduce function whenever typescript is enabled.

The code works just fine, but after I upgraded to the latest TS it is throwing the error and it does not compile

Can someone help me?

I have tried the following:

const sampleData = [{ valueObj: { High: 4, Low: 5,  Medium: 7 }, time: "1571372233234", sum: 16 }, { valueObj: { High: 5, Low: 3, Medium : 1 }, time: "1571372233234", sum: 9 }, { time: "14354545454", sum: 0 }, { time: "14354545454", sum: 0 }];

const keys = ['High', 'Low', 'Medium'];

function formResult(sampleData, prop, keys){
  let grouped = sampleData.reduce((r, { [prop]: values = {} } = {}) => {
    r.forEach(({ name, data }) => data.push(values[name] || 0));       
    return r;
  }, keys.map(name => ({ name, data: [] })));
  console.log(grouped);
}

formResult(sampleData,"valueObj", keys);

Advertisement

Answer

Using reduce with dynamic keys with Typescript can be ugly and is arguably not appropriate even in Javascript when creating a single object. Consider creating the object you put the data into outside the loop instead – one whose properties are the names (High, etc) and values are the number arrays. Push to the number array on each iteration (pushing 0 if the property doesn’t exist), creating the property with the array first if needed. After looping, turn the object into an array of objects:

// compliant with noImplicitAny and strict options
type DataItem = {
    time: string;
    sum: number;
    valueObj?: {
        High: number;
        Medium: number;
        Low: number;
    }
};
const sampleData: DataItem[] = [
    { valueObj: { High: 4, Low: 5,  Medium: 7 } , time: "1571372233234" , sum: 16 },
    { valueObj: { High: 5, Low: 3, Medium : 1 }, time: "1571372233234" , sum: 9},
    { time: "14354545454", sum: 0},
    { time: "14354545454", sum: 0}
];
const keys = ['High', 'Low', 'Medium'] as const;
const grouped = {} as { [key: string]: number[] };
for (const item of sampleData) {
    for (const key of keys) {
        if (!grouped[key]) {
            grouped[key] = [];
        }
        grouped[key].push(item.valueObj ? item.valueObj[key] : 0);
    }
}
const output = Object.entries(grouped).map(([name, data]) => ({ name, data }));

Compiled output:

"use strict";
const sampleData = [
    { valueObj: { High: 4, Low: 5, Medium: 7 }, time: "1571372233234", sum: 16 },
    { valueObj: { High: 5, Low: 3, Medium: 1 }, time: "1571372233234", sum: 9 },
    { time: "14354545454", sum: 0 },
    { time: "14354545454", sum: 0 }
];
const keys = ['High', 'Low', 'Medium'];
const grouped = {};
for (const item of sampleData) {
    for (const key of keys) {
        if (!grouped[key]) {
            grouped[key] = [];
        }
        grouped[key].push(item.valueObj ? item.valueObj[key] : 0);
    }
}
const output = Object.entries(grouped).map(([name, data]) => ({ name, data }));
console.log(output);

If the valueObj key is dynamic, it gets a lot uglier. Best I could figure out was, when iterating over an array item, if it has the own property of the object key, then to assert that the array item is of type { [possibleKeyForObj: string]: { [key: string]: number } }, allowing you to access the nested property:

const formResult = (
    sampleData: object[],
    possibleKeyForObj: string,
    keys: string[],
) => {
    const grouped = Object.fromEntries(keys.map(key => [key, []]));
    for (const item of sampleData) {
        for (const key of keys) {
            grouped[key].push(
                item.hasOwnProperty(possibleKeyForObj)
                    ? (item as { [possibleKeyForObj: string]: { [key: string]: number } })[possibleKeyForObj][key]
                    : 0,
            );
        }
    }
    const output = Object.entries(grouped).map(([name, data]) => ({ name, data }));
    console.log(output);
};

formResult(
    [
        { valueObj: { High: 4, Low: 5, Medium: 7 }, time: '1571372233234', sum: 16 },
        { valueObj: { High: 5, Low: 3, Medium: 1 }, time: '1571372233234', sum: 9 },
        { time: '14354545454', sum: 0 },
        { time: '14354545454', sum: 0 },
    ],
    'valueObj',
    ['High', 'Low', 'Medium'],
);
Advertisement