0
const cars = [
  { id: 'id1', name: 'ford' },
  { id: 'id2', name: 'audi' },
  { id: 'id3', name: 'bmw' },
] as const;
type CarId = typeof cars[number]['id'];

const carIndexes = cars.reduce((acc, car, i) => ({ ...acc, [car.id]: i }), {});
console.log(carIndexes);

This code works fine, and prints out

{ id1: 0, id2: 1, id3: 2 }

carIndexes gives me a map that allows me to look up the index of each car by their id, but I would like carIndexes to be strongly typed. When I change its type signature to { [K in CarId]: number }:

const carIndexes: { [K in CarId]: number } = cars.reduce((acc, car, i) => ({ ...acc, [car.id]: i }), {});

I get the error:

error TS2739: Type '{}' is missing the following properties from type '{ id1: number; id2: number; id3: number; }': id1, id2, id3

I would like to use a plain object, rather than a Map, because an object with type { [K in CarId]: number } guarantees that looking up the index can't fail for any CarId.

3 Answers 3

1

AFAIK, without casting manually to {[K in CarId]: number}, there is no way for typescript to detect that cars.reduce((acc, car, i) => ({ ...acc, [car.id]: i }), {}) (or other ways) will return an object with the whole CardId as index.

Sign up to request clarification or add additional context in comments.

Comments

0

I think you will have to explicitly type the reduce accumulator value:

const cars = [
    { id: 'id1', name: 'ford' },
    { id: 'id2', name: 'audi' },
    { id: 'id3', name: 'bmw' },
] as const;
type CarId = typeof cars[number]['id'];

const carIndexes = cars
    .reduce(
        (acc, car, i) => (
            { ...acc, [car.id]: i }
        ),
        {} as { [K in CarId]: number }
    );
console.log(carIndexes);

Comments

0

The best attempt at type safety in this case might be to use corresponding Partial type:

let carIndexes: Partial<{ [key in CarId]: number }> = {};
for (let i = 0; i < cars.length; ++i) {
  carIndexes[cars[i].id] = i;
  
  // note that following line would not compile due to TS2339: Property 'OTHER_PROPERTY' does not 
  // exist on type 'Partial<{ id1: number; id2: number; id3: number; }>'
  // carIndexes.OTHER_PROPERTY = 0;
}

The use of reduce() function proposed by some other answers and involving an explicit cast, in fact, ensures almost no static type safety. For instance, if I make a typo like following, the code still compiles:

const carIndexes: { [K in CarId]: number } = cars.reduce(
  (acc, car, i) => ({ 
    ...acc,
    //following line should not compile as there is no property "id" on typeof carIndexes
    id: i 
  }),
  {} as {[K in CarId]: number}
);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.