Skip to content

Add an overload to Object.fromEntries when called with const tuples #50379

Closed as not planned

Description

lib Update Request

Object.fromEntries as is declared today results in a loss of key names and a unionization of all values, or a complete loss of information and type of any.
This change would make it so that it is possible to create strict objects — from const tuples only.

Note that the usage of fromEntries with Arrays and non-readonly tuples cannot be made sound, so this issue only discusses the narrow case of const tuples.

Sample Code

const obj1 = Object.fromEntries([
  ['1', 2],
  ['3', 4],
] as const)
// this should result in an object of type: {1: 2, 3: 4}, but results instead in: { [k: string]: 2 | 4 }

const obj2 = Object.fromEntries([
  ['1', 2],
  ['3', '4'],
] as const)

// should result in an object of type: {1: 2, 3: '4'}, but results in: any

The how & example

This could be achieved by adding a new overload to Object.fromEntries in the case it is called with const tuples only.
This distinction is vital, because not distinguishing between Arrays, tuples and const tuples would make this change unsound. Only properties of a readonly tuple are guaranteed to be there at runtime, since they cannot be reassigned.

Here's a working proof-of-concept playground that should be sound in all cases, including the edge case of using a union type for the key.

The type is somewhat complex right now, so it would be great if we could somehow simplify it. Suggestions are welcome.

Related issues were previously closed, because the change would have been unsound without guarding for const tuples only (#35745, #49305, #43332). Here however, the discussion is around const tuples only.

Configuration Check

My compilation target is es2022 and my lib is es2022.

Missing / Incorrect Definition

  • Object.fromEntries

Note

Opened by request of Nathan Shively-Sanders (@sandersn) in #50203.

Activity

  1. webstrand commented on Aug 24, 2022

    @webstrand
    Contributor

    Another failing test case

    const x = fromEntries([] as [["foo", 1]] | Iterable<["bar", 2]>);
  2. webstrand commented on Aug 24, 2022

    @webstrand
    Contributor

    I really would rather avoid a conditional return type, at least not as the last overload. By supporting a reduced, but still improved, set of features I think this is better:

    // Most generally useful
    declare function fromEntries<T extends readonly [PropertyKey, unknown]>(
      entries: Iterable<T>
    ): { [P in T as P[0]]?: P[1] }
    
    // Support wonky union types like `[["foo", 1]] | Iterable<["bar", 2]>`
    declare function fromEntries<K extends PropertyKey, V>(
      entries: Iterable<readonly [K, V]>
    ): { [_ in K]?: V }
    
    // For people destructuring `fromEntries`
    declare function fromEntries(entries: Iterable<readonly [PropertyKey, unknown]>): { [key: PropertyKey]: unknown }

    I just noticed that this breaks the obj2_fromMixed case

  3. RyanCavanaugh commented on Aug 25, 2022

    @RyanCavanaugh
    Member

    This is extremely clever, but far too complex to put into the standard library. Since this can merge in at the top of the overload list, you can still get the same behavior in userland with a custom additional lib file.

  4. niieani commented on Aug 26, 2022

    @niieani
    Author

    Ryan Cavanaugh (@RyanCavanaugh) thanks for taking a look. I can make an NPM library for that, sounds like the way to go, unfortunately.

    It would be possible to make this much simpler, if we had these two things built-in in TypeScript:

    • When doing a mapped type, ability to conditionally set optionality.
      Right now, one can set the value using the X extends Y ? ... : ... pattern, but one cannot do it to add in or remove ? conditionally.
      Perhaps a special type like +? or -? to add or remove optionality in mapped types, and similarly to add or remove readonly property +readonly could be added? E.g. { [K in keyof O]: O[K] extends X ? X & +? : X & -? }
    • Ability to refine based on negation. E.g. only const tuples: T extends readonly [...unknown] & not [...unknown]

    webstrand the failing test case you mentioned is failing for a good reason - there's no way to create a predictable object from that type, so it should be handled by another overload and fallback to a less strictly typed type. And as you notice, your simplified version unfortunately isn't sound. Believe me I've spent hours trying to figure it out.

  5. alexweej commented on Nov 21, 2022

    @alexweej

    Yes, please!

  6. niieani commented on Feb 8, 2023

    @niieani
    Author

    I've finally gotten around to publishing an NPM package with the strict Object.fromEntries typings.
    See nesity-types.

  7. sandersn commented on Feb 8, 2023

    @sandersn
    Member

    Would it be OK to close this PR now?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    SuggestionAn idea for TypeScriptToo ComplexAn issue which adding support for may be too complex for the value it adds

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions