TypeScript: Object.keys return string[]

When using Object.keys(obj), the return value is a string[], whereas I want a (keyof obj)[].

const v = {
    a: 1,
    b: 2
}

Object.keys(v).reduce((accumulator, current) => {
    accumulator.push(v[current]);
    return accumulator;
}, []);

I have the error:

Element implicitly has an 'any' type because type '{ a: number; b: number; }' has no index signature.

TypeScript 3.1 with strict: true. Playground: here, please check all checkboxes in Options to activate strict: true.


Object.keys returns a string[]. This is by design as described in this issue

This is intentional. Types in TS are open ended. So keysof will likely be less than all properties you would get at runtime.

There are several solution, the simplest one is to just use a type assertion:

const v = {
    a: 1,
    b: 2
};

var values = (Object.keys(v) as Array<keyof typeof v>).reduce((accumulator, current) => {
    accumulator.push(v[current]);
    return accumulator;
}, [] as (typeof v[keyof typeof v])[]);

You can also create an alias for keys in Object that will return the type you want:

export const v = {
    a: 1,
    b: 2
};

declare global {
    interface ObjectConstructor {
        typedKeys<T>(obj: T): Array<keyof T>
    }
}
Object.typedKeys = Object.keys as any

var values = Object.typedKeys(v).reduce((accumulator, current) => {
    accumulator.push(v[current]);
    return accumulator;
}, [] as (typeof v[keyof typeof v])[]);

Based on Titian Cernicova-Dragomir answer and comment

Use type assertion only if you know that your object doesn't have extra properties (such is the case for an object literal but not an object parameter).

Explicit assertion

Object.keys(obj) as Array<keyof typeof obj>

Hidden assertion

const getKeys = Object.keys as <T extends object>(obj: T) => Array<keyof T>

Use getKeys instead of Object.keys. getKeys is a ref to Object.keys, but the return is typed literally.

Discussions

One of TypeScript’s core principles is that type checking focuses on the shape that values have. (reference)

interface SimpleObject {
   a: string 
   b: string 
}

const x = {
   a: "article", 
   b: "bridge",
   c: "Camel" 
}

x qualifies as a SimpleObject because it has it's shape. This means that when we see a SimpleObject, we know that it has properties a and b, but it might have additional properties as well.

const someFunction = (obj: SimpleObject) => {
    Object.keys(obj).forEach((k)=>{
        ....
    })
}

someFunction(x)

Let's see what would happen if by default we would type Object.keys as desired by the OP "literally":

We would get that typeof k is "a"|"b". When iterating the actual values would be a, b, c. Typescript protects us from such an error by typing k as a string.

Type assertion is exactly for such cases - when the programmer has additional knowledge. if you know that obj doesn't have extra properties you can use literal type assertion.


See https://github.com/microsoft/TypeScript/issues/20503.

declare const BetterObject: {
  keys<T extends {}>(object: T): (keyof T)[]
}

const icons: IconName[] = BetterObject.keys(IconMap)

Will retain type of keys instead of string[]