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

Typed Object.entries/Object.fromEntries - Or Type Utility to infer Unions with matching length so that a new type safe object can be made #60859

Open
6 tasks done
Jonathan002 opened this issue Dec 26, 2024 · 4 comments
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript

Comments

@Jonathan002
Copy link

Jonathan002 commented Dec 26, 2024

🔍 Search Terms

"Typed Object.entries", "Typed Object.fromEntries", "Object.fromEntries", "Object.entries", "Infer Union Types"

✅ Viability Checklist

⭐ Suggestion

I have an issue where I'm trying to Match Two Union Types together to make Object.entries and map() back to a transformed Object.fromEntries(). I believe I almost can do it, but may lack the typescript tool to do a correct inference to make sure two unions with the same length and match up properly.

I would like to refer to this stack overflow question I have asked: https://stackoverflow.com/questions/79310390/i-need-help-strongly-typing-typescript-object-entries?noredirect=1#comment139855755_79310390

I think Object.entries() and adding transformations to the Value is a common operation. It would be amazing if typescript had implicitly made it easy for it to correctly become a typed object with Object.fromEntries().

Preferable Feature:
The feature suggestion hopes that either native es Object.fromEntries( Object.entries().map(([key, val]) => [key, () => val]) ) can infer value transformations and match a key automatically.


Alternative Feature:
Type utilities to make the above possible if the user has to create their own types. Currently I find it difficult to work with generic unions that match the same length and match them to map back to an object. You can view my code question: https://stackoverflow.com/questions/79310390/i-need-help-strongly-typing-typescript-object-entries?noredirect=1#comment139855755_79310390 and notice that I'm trying to Extract<Val, Val> in the case it comes back as a union. The code already feels complex. It would be great if additional type tools can help with Entry Types or Unions with the same length. Maybe a syntax to declaratively state EntryUnion<UnionOne, UnionTwo, FallbackValue> which would do its best to match each union index accordingly if the lengths fit or go to a fallback value if UnionTwo does not fit UnionOne length.

// Note: The union matching to create a working Object.fromEntries() works if we incorrectly use infer _V.
// ------------------------------------------------------------
// Context:
// Entry: ["fruitName", "apple"] | ["description", string] | ["weight", number] | ["isFresh", boolean]
  // infer K: "fruitName" | "description" | "weight" | "isFresh"
  // infer _V: "apple" | string | number | boolean
// Val: 
  // - Example 1: 123 - number
  // - Example 2: entry[1] - "apple" | string | number | boolean - Same union as infer _V but does not key match with infer K
  // - Example 3: () => entry[1] - () => string | number | boolean
  // - Example 4: { test: entry[1] } - { test: string | number | boolean }
type TransformEntry<Entry, Val> = Entry extends [infer K, infer _V] ? [K, Extract<Val, Val>] : never;

📃 Motivating Example

Please refer to this stack overflow question and view the examples: https://stackoverflow.com/questions/79310390/i-need-help-strongly-typing-typescript-object-entries?noredirect=1#comment139855755_79310390

JS has a large api that deal with the concept of entries that work with Object.fromEntries() that will eventually become an object. If we can parse these patterns as special, it would be more type safe rather than doing type assertions to correct the output.

  • Object.entries
  • new Map().entries()
  • new Set().entries()
  • Array.entries()

  • Object.fromEntries()

💻 Use Cases

  1. What do you want to use this for?
  • I would use this with everyday Object transformation of values. It would be amaizing to just iterate objects and tap into a value update with seemless type inference.
  1. What shortcomings exist with current approaches?
    The current approach is more tedious and less type safe. Most consumers apparently just use type assertion when doing Object.fromEntries(Object.entries()) which can be prone to human error.

  2. What workarounds are you using in the meantime?
    This example in the stack overflow question is what I use: https://stackoverflow.com/questions/79310390/i-need-help-strongly-typing-typescript-object-entries?noredirect=1#comment139855755_79310390 or sometimes my own type assertion.

@Jonathan002 Jonathan002 changed the title Ability to infer matching Union Types so that an object can be reformed. Typed Object.entries/Object.fromEntries - Or Type Utility to infer Unions with matching length so that a new type safe object can be made Dec 26, 2024
@jcalz
Copy link
Contributor

jcalz commented Dec 27, 2024

It's not just matching length, it's that each entry key might correspond to a different value type. Lengths can be handled, arbitrary type mappings cannot. If TS gets higher kinded types like #1213, or maybe call types like #40179, then I think map() could be rewritten to handle what you're doing, but without that, I don't see it.

@MartinJohns
Copy link
Contributor

It's also important to mention that the actual values returned by Object.entries() can contain keys not present on the type, because objects are not sealed in TypeScript. For this alone I can't really see any improvements to the typings of Object.entries().

@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature labels Jan 2, 2025
@HansBrende
Copy link

Simpler repro (as far as I understand at least):

const result = ([1, 2, 3] as const).map(x => ({x}));

// Expected type: [{x: 1}, {x: 2}, {x: 3}]  (or have a way to type this outcome)
// Actual type: {x: 1 | 2 | 3}[]

@HansBrende
Copy link

Interesting: looks like it is currently almost possible if only the inference of function return types were just slightly smarter:

type FunctionOfKeys<T, K extends keyof T = keyof T & `${number}`> = 
    UnionToIntersection¹<K extends unknown ? (x: T[K]) => unknown : never>

type MapTuple<T, F extends FunctionOfKeys<T>> = {[K in keyof T]: F extends (x: T[K]) => infer R ? R : never}

type MappingFunction = <T extends 1|2|3>(x: T) => {x: T}

type Result = MapTuple<[1, 2, 3], MappingFunction>
//    ^?   type Result = [{x: 1|2|3}, {x: 1|2|3}, {x: 1|2|3}]
// vs. Expected Result = [{x: 1}, {x: 2}, {x: 3}]

type MappingFunction2 = ((x: 1) => {x: 1}) & ((x: 2) => {x: 2}) & ((x: 3) => {x: 3})
type Result2 = MapTuple<[1, 2, 3], MappingFunction2>
//    ^? type Result2 = [never, never, {x: 3}]

// So close :(

¹@jcalz. "You want union to intersection?" StackOverflow (2018)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

5 participants