Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support of fromEntries #26149

Closed
wants to merge 1 commit into from
Closed

Conversation

Kingwl
Copy link
Contributor

@Kingwl Kingwl commented Aug 2, 2018

Fixes #25999

* Returns an object from an array of properties key/values
* @param entries Array that contains the properties key and value.
*/
fromEntries(entries: ArrayLike<[string, any]> | Iterable<[string, any]>): { };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

either Record<string, any> or object. {} is the wrong type here.

* Returns an object from an array of properties key/values
* @param entries Array that contains the properties key and value.
*/
fromEntries<T>(entries: ArrayLike<[string, T]> | Iterable<[string, T]>): { [ s: string]: T };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider using Record<string, T> instead.

@@ -0,0 +1,13 @@
interface ObjectConstructor {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RyanCavanaugh the new file will need to be added to the setup of the VS SDK. you will need to run VS\scripts\distributeLibFiles.bat to update the setup.

@RyanCavanaugh RyanCavanaugh self-assigned this Aug 6, 2018
* Returns an object from an array of properties key/values
* @param entries Array that contains the properties key and value.
*/
fromEntries<T>(entries: ArrayLike<[string, T]> | Iterable<[string, T]>): Record<string, T>;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to support tuple types but not sure the compiler is clever enough to deal with that without having to define 200 overloads

fromEntries<K1 extends string, V1>(entries: [[K1, V1]]): { [K1]: V1 }
fromEntries<K1 extends string, V1, K2 extends string, V2>(entries: [[K1, V1], [K2, V2]]): { [K1]: V1; [K2]: V2 }
// ...

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was nice if typescript had any way to declare types mapping from tuple to object

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a real use case for Object.fromEntries with a fixed number of entries? Seems like almost always you can just use object literal syntax in a case like that.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a use case for that :)
im doing a-lot of these manipulations.
and if i had a generic way to express it, that would be awesome

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Bnaya can you share a realistic code snippet where object literal syntax doesn't work but fixed-length fromEntries does?

@RyanCavanaugh RyanCavanaugh added this to the TypeScript 3.2 milestone Sep 17, 2018
@alangpierce
Copy link
Contributor

I want to call out a usability issue I've seen when working with this API: tuple types aren't inferred from array literals in typical situations for the "object comprehension" use case.

For example, this snippet doesn't typecheck:

const thingsById = Object.fromEntries(things.map((thing) => [thing.id, thing]));

The underlying error is Type '(string | Thing)[]' is not assignable to type '[string, Thing]'., which happens because [thing.id, thing] is treated as an array of string | Thing rather than a tuple type.

As one workaround, I can make my own function entry that forces the type to be a tuple:

function entry<T>(key: string, value: T): [string, T] { return [key, value]; }
const thingsById = Object.fromEntries(things.map((thing) => entry(thing.id, thing)));

but certainly that's not ideal since it's not in the JS standard library.

Maybe the typechecker could be smarter here and either try both variants (array and tuple) or see from context that we prefer a tuple return type from the arrow function rather than an array type?

@Jessidhia
Copy link

This can already be encountered today when mapping data for passing to a Map constructor, this will just be another place where it happens.

Tangentially related, I encounter this more commonly now in my codebase when writing custom React hooks.

As a really simple custom hook, take:

function useLoggedState<T>(initialValue: T | (() => T) {
  const [value, setValue] = React.useState(initialValue)
  useEffect(() => {
    console.log('value is', value)
  }, [value])
  return [value, setValue]
}

Letting typescript infer the return type produces (T | React.Dispatch<React.SetStateAction<T>>)[] which is not useful at all to users of the hook.

I've been fixing it with return [value, setValue] as [typeof value, typeof setValue]. This is probably the cleanest way to deal with it until we have some way to tell typescript that it's ok to infer a tuple from an array literal.

@aboyton
Copy link

aboyton commented Jan 17, 2019

It would be great if Object.fromEntries(Object.entries(foo)) returned something of the same type as foo where possible.

When foo is of type { [key: string]: T } this should be easy but it would be nice if this could also support { [key in Values]: T } and { [key in Values]?: number } as well.

enum Values {
  a = 'a',
  b = 'b',
  c = 'c',
}

const foo: { [key in Values]: number } = { a: 1, b: 2, c: 3 };
const bar = Object.fromEntries(Object.entries(foo));

* Returns an object from an array of properties key/values
* @param entries Array that contains the properties key and value.
*/
fromEntries(entries: ArrayLike<[string, any]> | Iterable<[string, any]>): Record<string, any>;
Copy link
Contributor

@saschanaz saschanaz Feb 7, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tc39/ecma262#1274

fromEntries only accepts iterable object so probably we should remove ArrayLike here. The Firefox implementation also throws for array-like object.

(Edit: Or replace it with ReadonlyArray if we want it to be ES5 compatible.)

@saschanaz
Copy link
Contributor

@Kingwl Object.fromEntries just hit stage 4 as (yet incomplete) ES2019 standard. Would you fix this PR to use es2019.object instead?

@Kingwl
Copy link
Contributor Author

Kingwl commented Feb 9, 2019

@saschanaz sure, after a few moments later

* Returns an object from an array of properties key/values
* @param entries Array that contains the properties key and value.
*/
fromEntries<T>(entries: ArrayLike<[string, T]> | Iterable<[string, T]>): Record<string, T>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Drive-by comment: fromEntries allows Symbol keys, not just strings.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

😸

The more comprehensive version is:

  1. The first argument must be an Iterable. The algorithm literally calls @@iterator. Can't get away with ArrayLikes here.
  2. The iterable must yield an object of the shape { 0?: string | number | symbol | null | { valueOf(): string | number } | { toString(): string }; 1?: any }. This is probably not very useful for the actual type definition, and is incomplete as the valueOf may just be any other coercible value; but the point is that the Iterable's value needs not be an array or ArrayLike.
    2a) The "0" key / first element of tuple just needs to be something that can be cast into an object key, and that includes undefined. undefined becomes "undefined" and null becomes "null". Pretty much the only thing I found that "doesn't work" is Object.create(null).
    2b) The "1" key / second element of tuple is the value associated with the key.
    2c) Any of those keys may be getters or just missing entirely. They may even be write-only setters.
  3. Duplicates overwrite each other in iteration order. The last value wins.

Current TypeScript treats tuples as assignable to objects with numeric literal keys. Using <K extends keyof any, T> as the generic argument and Iterable<{ 0: K, 1: T }> should work for most cases; with maybe a few more overloads for index signatures and a catch-all for mixed tuples.

@ljharb
Copy link
Contributor

ljharb commented Apr 17, 2019

This proposal is now stage 4, in ES2019.

Is there any possibility of making the types for this function take an iterable of entries, where an Entry is a tuple-2 of "K extends PropertyKey, V extends unknown", and returns an object consisting of all the K: V pairs in entries? It seems a shame if the type needs to end up being something less magical.

@Jessidhia
Copy link

Jessidhia commented Apr 17, 2019

<K extends keyof any, T>(entries: Iterable<{ 0: K, 1: T }>): Record<K, T> will do, but unfortunately typescript can't represent an iterable that yields different types over time. Another overload would be needed if the 1 is optional ({ 0: K, 1?: T }, which would have T | undefined as the resulting value type.

It's possible to do something like <K0 extends keyof any, T0, K1 extends keyof any, T1, ...>(entries: [{ 0: K0, 1: T0 }, { 0: K1, 1: T1 }, ...]): {[K in K0 | K1 | ...]: K extends K0 ? T0 : K extends K1 ? T1 : ...}, or otherwise use intersection types, but you basically need an extra overload for each arity, and you have to use tuple types instead of iterable types.

@bakkot
Copy link
Contributor

bakkot commented Apr 17, 2019

It seems reasonable to me that the return type is no more precise than Record<K, T>. That's consistent (ish) with the inverse function, anyway.

@RyanCavanaugh
Copy link
Member

I think we'll pick this up at #30934. It would be great if people could write up concise example testcases they think should pass / should fail for the definition WIP

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

proposal-object-from-entries