riki yudha
riki yudha

Reputation: 55

How to iterate over known property keys in TypeScript?

This is my code:

interface Siswa {
  name: string,
  points: { a: number, b: number, c: number, d?: number, e?: number }
}

function find_average_points(data: Array<Siswa>): Array<{name: string, average: number}> {
  let returned_array: Array<{name: string, average: number}> = [];
  data.forEach((item: Siswa) => {
    let sum: number = 0;
    let keys = Object.keys(item.points);
    keys.forEach((key: any) => sum+=item.points[key]);
    returned_array.push({name: item.name, average: (sum/keys.length)});
  });
  return returned_array;
}

When I tried this function in JavaScript, it ran correctly and the result is what I want, but in TypeScript, I got an error in item.points[key]. It says:

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ a: number; b: number; c: number; d?: number | undefined; e?: number | undefined; }'.
  No index signature with a parameter of type 'string' was found on type '{ a: number; b: number; c: number; d?: number | undefined; e?: number | undefined; }'

I don't know what it means.

Upvotes: 0

Views: 344

Answers (2)

vaira
vaira

Reputation: 2270

You can use a combination of keyof and null check to solve the problem.

    interface Points { a: number, b: number, c: number, d?: number, e?: number } // only did to use keyof

interface Siswa {
  name: string,
  points: Points
}

function find_average_points(data: Array<Siswa>): Array<{name: string, average: number}> {
  let returned_array: Array<{name: string, average: number}> = [];
  data.forEach((item: Siswa) => {
    let sum: number = 0;
    let keys = Object.keys(item.points) as (keyof Points)[]; // use this if you know you wont add extra non number properties in Point 

    keys.forEach((key) => sum+=item.points[key] ?? 0); // for d and e with are optional
    returned_array.push({name: item.name, average: (sum/keys.length)});
  });
  return returned_array;
}
let obj: Siswa = { name: 'Paris', points: { a: 2 , b:2, c: 4, d: 4 }} // can not add random stuff type safe

console.log(find_average_points([obj]))

Upvotes: 1

jsejcksn
jsejcksn

Reputation: 33931

Using Object.keys() returns the type string[] (as explained in this comment). Instead, you can create arrays for the known keys in the points object, use them to build your types, and then loop through those keys when summing the values:

TS Playground link

const definitePoints = ['a', 'b', 'c'] as const;
const maybePoints = ['d', 'e'] as const;

type Points = (
  Record<typeof definitePoints[number], number>
  & Partial<Record<typeof maybePoints[number], number>>
);

interface Siswa {
  name: string,
  points: Points;
}

interface SiswaAverage {
  name: string;
  average: number;
}

function find_average_points (data: Array<Siswa>): Array<SiswaAverage> {
  const result: Array<SiswaAverage> = [];

  for (const {name, points} of data) {
    let sum = 0;
    let count = 0;

    for (const point of [...definitePoints, ...maybePoints]) {
      const value = points[point];
      if (typeof value !== 'number') continue;
      sum += value;
      count += 1;
    }

    result.push({name, average: sum / count});
  }

  return result;
}

Upvotes: 3

Related Questions