-
Notifications
You must be signed in to change notification settings - Fork 13.2k
Description
Search Terms
Object.entries
Potentially related issues:
Suggestion
Typings of Object.entries() returned [key, value] tupple array is very weak and infer poorly from argument, I think it would be better to implement more relevant ones in lib.es2017.object.d.ts
Use Cases
When iterating on an object entries array returned by Object.entries, we need to manually type cast the array to avoid false positive and false negative typings shortcomings.
const a = Object.entries({foo: "bar", baz: 0}); // [string, string | number][]
const b = Object.entries({foo: "bar", baz: 0} as const); // [string, 0 | "bar"][]It is not possible to infer typings in .mapor .forEach
a.forEach(entry => {
if(entry[0] === "foo") {
let value = entry[1]; // string | number
entry[1].toUpperCase(); // error (false positive)
} else if (entry[0] === "baz") {
let value = entry[1]; // string | number
} else if (entry[0] === "invalid") { // no error (false negative)
// ...
}
})
b.forEach(entry => {
if(entry[0] === "foo") {
let value = entry[1]; // 0 | "bar"
}
})Examples
We can easily write our own entries function:
type Entries<T> = {
[K in keyof T]: [K, T[K]]
}[keyof T][]
function entries<T>(obj: T): Entries<T> {
return Object.entries(obj) as any;
}Wich gives me an array of tupples with inferables key/value:
const c = entries({foo: "bar", baz: 0}); // (["foo": string] | ["baz": number])[]
const d = entries({foo: "bar", baz: 0} as const); // (["foo": "bar"] | ["baz": 0])[]
c.forEach(entry => {
if (entry[0] === "foo") {
let value = entry[1]; // string
entry[1].toUpperCase(); // no error
} else if (entry[0] === "baz") {
let value = entry[1]; // number
}
if (entry[0] === "invalid") { // error: "baz" and "invalid" have no overlap
// ...
}
});
d.forEach(entry => {
if (entry[0] === "foo") {
let value = entry[1]; // "bar"
} else if (entry[0] === "baz") {
let value = entry[1]; // 0
}
if(entry[1] === "bar") {
let key = entry[0]; // "foo"
}
});We may implement a better default behaviour for Object.entries in the lib:
/**
* Returns an array of key/values of the enumerable properties of an object
* @param o Object that contains the properties and methods. This can be an object that you created or an existing Document Object Model (DOM) object.
*/
entries<T>( o: T): T extends ArrayLike<infer U> ? [string, U][] : { [K in keyof T]: [K, T[K]] }[keyof T][];The same concepts can be applied for better typings of Object.values
/**
* Returns an array of values of the enumerable properties of an object
* @param o Object that contains the properties and methods. This can be an object that you created or an existing Document Object Model (DOM) object.
*/
values<T>(o: T): T extends ArrayLike<infer U> ? U[] : (T[keyof T])[];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. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
- This feature would agree with the rest of TypeScript's Design Goals.
- This wouldn't be a breaking change in existing TypeScript/JavaScript code
I need some feedback to identify potential breaking changes.
I think it 'may' induce breaking change since it's reducing typecast necessity. If manual casts are incompatible with the current feature request, there may be a need to remove cast or force through an intermediary any.
I tested it in a project of mine and had no issue since the existing cast was correctly made with the same source of truth as the Object.values argument type.
- This feature would agree with the rest of TypeScript's Design Goals.
I can't say I'm sure it does. Feedback would be appreciated.