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

Object.entries broken with object literals #56

Open
danielrentz opened this issue Jan 9, 2025 · 6 comments
Open

Object.entries broken with object literals #56

danielrentz opened this issue Jan 9, 2025 · 6 comments

Comments

@danielrentz
Copy link
Contributor

The value type of object literals becomes unknown.

  • before (built-in TS lib):
const entries1 = Object.entries({ a: 1, b: 2 });           // => [string, number][]
const entries2 = Object.entries({ a: 1, b: 2 } as const);  // => [string, 1|2][]
  • after (with better-ts-lib):
const entries1 = Object.entries({ a: 1, b: 2 });           // => [string, unknown][]
const entries2 = Object.entries({ a: 1, b: 2 } as const);  // => [string, unknown][]
@danielrentz
Copy link
Contributor Author

danielrentz commented Jan 9, 2025

We are using the following workaround in our projects:

interface ObjectConstructor {
    entries<T extends object>(source: T): [keyof T & string, T[keyof T]][];
}

@ehoogeveen-medweb
Copy link

FWIW, I suggested the following implementations in #31 (comment):

type KeyToString<K extends PropertyKey> = K extends string ? K : K extends number ? `${K}` : never;

interface ObjectConstructor {
  keys<K extends PropertyKey, V>(o: [K, V] extends [never, never] ? never : Record<K, V>): KeyToString<K>[];
  values<K extends PropertyKey, V>(o: [K, V] extends [never, never] ? never : Record<K, V>): V[];
  entries<K extends PropertyKey, V>(o: [K, V] extends [never, never] ? never : Record<K, V>): [KeyToString<K>, V][];
}

Where the extends [never, never] thing prevents matching object and returning a nonsense type. However, I'm not sure how this interacts with the changes discussed in #46

@uhyo
Copy link
Owner

uhyo commented Jan 10, 2025

I see how this is inconvenient, but I don't think this can be made better without breaking type safety.

Specifically, code like below would break type safety if you are using that workaround.

type A = { a: number; b: number };

const obj = { a: 1, b: 2, c: "Hey!" };

const obj2: A = obj;

const entries = Object.entries(obj2);
//    ^?

To mitigate this we need exact types, which isn't supported by TS yet.

That said, I think I need more time to evaluate the solution proposed by @ehoogeveen-medweb here.

@danielrentz
Copy link
Contributor Author

Ah I see. And this has been discussed in #2 already (I did not scan the closed issues before ...)

@ehoogeveen-medweb
Copy link

I think the fact that typescript allows excess properties makes it fundamentally impossible to have "optimistic" signatures that are fully safe:

type KeyToString<K extends PropertyKey> = K extends string ? K : K extends number ? `${K}` : never;

declare function entries<K extends PropertyKey, V>(o: [K, V] extends [never, never] ? never : Record<K, V>): [KeyToString<K>, V][];

type A = { a: number; b: number };

const obj1 = { a: 1, b: 2, c: "Hey!" } as const;
const obj2: A = obj1;

const entries1 = entries(obj1); // ["a" | "b" | "c", 2 | 1 | "Hey!"][]
const entries2 = entries(obj2); // [keyof A, number][]

for (const [key, value] of entries2) {
  // TS2367: This comparison appears to be unintentional because the types 'keyof A' and '"c"' have no overlap.
  if (key == 'c') {
    // Excess property with unknown value type
  }
}

playground

So theoretically Object.entries should return type [PropertyKey, unknown][] in all cases to be safe (as the object may contain any extra property of any type). I think this significantly reduces the utility of TypeScript though...

@ehoogeveen-medweb
Copy link

ehoogeveen-medweb commented Jan 10, 2025

Er, I guess not PropertyKey for the key type as Object.entries only returns string-keyed, enumerable own properties. So [string, unknown][], probably - but you get my point.

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

No branches or pull requests

3 participants