Improve Object.keys(object) and Object.values(object)
KSXGitHub opened this issue · 5 comments
Search Terms
Suggestion
When all keys and values of object is known
Object.keys(object)should return array type of union of known propertiesObject.values(object)should return array type of union of known values
How to implement?
I tried creating the 2 functions:
declare function keys<
Object extends { [_: string]: any },
Key extends keyof Object
> (obj: Object): Key[]
declare function values<
Object extends { [_: string]: any },
Key extends keyof Object
> (obj: Object): Object[Key][]I also tested them: playground link
Use Cases
Use with objects
const object = {
abc: 123 as 123,
def: 456 as 456
}
const objectKeys = Object.keys(object) // objectKeys: Array<'abc'|'def'>
const objectVals = Object.values(object) // objectVals: Array<123|456>Use with enums
enum Foo {
abc,
def = 'def'
}
const FooKeys = Object.keys(Foo) // FooKeys: Array<'abc' | 'def'>
const FooVals = Object.values(Foo) // FooVals: Foo[]Caveats
It is possible to ignore extra keys/values that don't exist in type definition
interface Foo {
abc: 'abc' // the only known key/value pair of Foo
}
const bar = {
abc: 'abc' as 'abc', // exist in Foo
ghi: 'ghi' as 'ghi' // doesn't exist in Foo
}
const foo: Foo = bar
const keys = Object.keys(foo) // TS emits `keys: 'abc'[]` but the correct value is `['abc', 'def']`Examples
See use cases and playground link above
Checklist
My suggestion meets these guidelines:
- This wouldn't be a breaking change in existing TypeScript / JavaScript code
- This wouldn't change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn't a runtime feature (e.g. new expression-level syntax)
Duplicate of this closed PR #25832.
As an example why:
const object = {
abc: 123 as 123,
def: 456 as 456
}
const objectKeys = Object.keys<{abc: 123}, 'abc'>(object) // objectKeys: Array<'abc'> whoops, array will contain 'def'enum Foo {
abc,
def = 'def'
}
const FooKeys = Object.keys(Foo) // FooKeys: Array<'abc' | 'def'>
const FooVals = Object.values(Foo) // FooVals: Foo[]
keys of enum has not only "key"
While crawling through the issues I found this merged PR: #18175
Based on this precedence, would it make sense to type Object.keys as the union of all known properties and string[] for the unknown ones? Or is there simply no reasonable way to represent such a type?
@AlCalzone I think there is simply no way to represent this safely. Subtyping and this kind of reflection are incompatible because there is no correspondence between the value and the typescript type.
The reason getOwnPropertyDescriptors gets away with it is because you are immediately using the synthesised keys, and in the event the key doesn't exist, you can return a safe default value (PropertyDescriptor).
Dates back to at least #12253