Preserving type when using Object.keys()

5.6k views Asked by At

I have an object with typed keys, let's call them Statuses (StatusesType). I need to iterate over the object and pass keys to some method expecting parameter of the same type StatusesType, let it be statusPrinter()

type StatusesType = 'PENDING' | 'APPROVED' | 'REJECTED';
type SomeMap = {
    [key in StatusesType]?: number
}

const STATUSES: SomeMap = {
    PENDING: 5,
    REJECTED: 2,
};

function statusPrinter(val: StatusesType) {
    console.log('- ', val);
}

Object.keys(STATUSES).forEach(status => {
    statusPrinter(status);
});

But when I call statusPrinter(status); TypeScript returns this error

error TS2345: Argument of type 'string' is not assignable to parameter of type 'StatusesType'.

How can I pass this key preserving type?

I know that I can force TS with this statusPrinter(<StatusesType>status); but I think it is the last thing I should do and I would prefer native solution.

Update: If it is not possible to iterate over object keys with Object.keys() preserving type - what options do I have? Is there a way to iterate over keys preserving types at all, and if so - which way is the best? I am not fixing on Object.keys() but I would like to keep original object structure.

Thanks!

3

There are 3 answers

2
ideaboxer On BEST ANSWER

Short and typesafe solution using the built-in ES2015 Map class:

type StatusesType = 'PENDING' | 'APPROVED' | 'REJECTED';

const STATUSES = new Map<StatusesType, number>([
    ['PENDING', 5],
    ['REJECTED', 2],
]);

function statusPrinter(val: StatusesType) {
    console.log('- ', val);
}

STATUSES.forEach((_, status) => statusPrinter(status));
1
Rajesh On

Object.keys will return an array of keys and keys are of type string.

So signature of Object.keys would be key(object: {}): Array<string>. So when you loop over keys, status is of type string and not StatusesType.

You can cast the type though as statusPrinter(status as StatusesType)

Reference Link:

3
Pavel On

You can do that with iterator like this:

function iterator<M, K extends keyof M>(map: M, cb: (key: keyof M, value: M[K]) => void) {
  Object.keys(map).forEach((key: K) => cb(key, map[key]))
}

iterator(STATUSES, status => {
    statusPrinter(status);
});