-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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.values and Object.entries are unsound and inconsistent with Object.keys. #38520
Comments
It's an unfortunate situation but I can't imagine the upside to moving things in a direction that so many people would simply consider worse, even if it's more correct. I don't know if you've seen it, but we basically get a bug report or PR to change |
Yeah, I have seen many of the Personally, I think that the sound solution is preferable, but I understand that often the TS dev team makes soundness sacrifices for usability reasons. The main reason I didn't lead with a PR to "fix" |
Hi, I agree with you that First, when an object is used as a record (or "dictionary"), the current typing of const r: Record<string, number> = {
foo: 0,
bar: 1,
};
const vs = Object.values(r); // : number[]
const es = Object.entries(r); // : [string, number][] If they are typed as Second, the object spread operator has the same kind of unsoundness. const a = { foo: 0, bar: 1 }; // : { foo: number, bar: number }
const b = { foo: 2, bar: "bar" };
const c: { foo: number } = b;
const d = { ...a, ...c }; // : { foo: number, bar: number } As in the above code, the object spread operator What do you think? |
Aha, I found it finally! I personally do not agree the behavior of |
I think if Typescript is about improving developer experience, Object.keys should return |
If we are concerned about actual runtime type checking, TS team should develop a tool to actually check runtime types (during development only) |
+1 for moving Moving Typescript is not a language for improving your editor's autocomplete, it's a language for preventing type errors. Given how Typescript's type system works, it's safer to use an |
IMO that should be setting off huge alarm bells that the current typing of
|
That is still not type safe. TypeScript has no way to guarantee the fields you can iterate over when you’re iterating over all of an object’s keys. Const assertions prevent type widening, but it’s still possible to set arbitrary fields on them by asserting |
asserting |
Honestly, I don't understand this Object.keys situation. I don't recall seeing one good example of how typescript will mislead developers by using keyof T. I do have an npm package which gives you a well-typed alias. I have a stack overflow answer that details an npm package I created, |
This confused me too. Since it isn't mentioned here yet, I'll just say the reason for |
@ahejlsberg said
what about allow something like (keyof Base | string)[]? so everyone is happy (safety and autocomplete) (maybe typescript keeping some information about unions) |
@mdbetancourt I like the effort, but, try putting that into typescript playground:
|
yup i know :) that's the part of what about allow and |
Here's an example. That code will always throw a runtime error ( |
Frankly, arguing about the lack of type-safety is laughable considering the current state of Typescript. A simple example that comes to mind : const arr = ["a", "b"]
type V = typeof arr[number] // string, should be string | undefined
const x = arr[2] // string, should be string | undefined I agree with @mantljosh that we should consider cases where objects are frozen in priority. I very often stumble upon the return type of I understand there are limitations in the design, and not everything can be done. EDIT:
Yes, but with a frozen object |
@romain20100
At least
It's not the same as the original example (originally
Honestly, this example is manifestation of a different problem (untyped object literals + non-exact types). This problem can occur in different forms. That's why it's a good practice to ensure object literals are explicitly typed. NgRx even has an eslint rule for this (only for one specific case of the problem, though): https://ngrx.io/guide/eslint-plugin/rules/on-function-explicit-return-type |
@amakhrov it's not just about literals. The same problem occurs any time you have a subtype with additional properties (e.g. here's an example with classes). But you're right that the lack of exactness is implicated here—TypeScript types only ever describe a subset of the properties which will exist at runtime, so the callback given to |
An even more minimal example is type X = {a: unknown}
const f = (x: X) => Object.keys(x) // should expect ['a']
f({a: 1, b: 2}) // but you actually get `['a', 'b']` This reminds me of variance issues with generics as well |
I wrote out this appeal just over a year ago, which unfortunately was closed: #45835 I feel that we should still consider it, the crux being: Within a structural type-system and in the absence of Exact Types, it would appear that most Object reflection methods must be typed imprecisely wide, including But this kind of feels like it violates Typescripts third non-goal: Precise types for object methods are incredibly useful, which is clear by the number of requests, questions and PRs around Given the state of things it feels like one of two things should happen:
Sample CodeSomething like this maintains parity with the status-quo: interface ObjectConstructor {
keys<T extends Record<string, unknown>>(o: T): (keyof T)[];
} Which is not a valid program in the SO example, but demonstrates the same level of unsoundness that already exists The first part is just a bit of context for why I think one of the two above things should happen`@jcalz` has enumerated a non-exhaustive list [here](https://github.com//issues/45390#issuecomment-895661910), the issue template links to the [SO Answer](https://stackoverflow.com/questions/55012174/why-doesnt-object-keys-return-a-keyof-type-in-typescript). I'm aware of these resources, I know that this comes up quite frequently, but it would be greatly appreciated if you would hear me out 🙏 It's instructive to go way back to 2016 with this comment in a follow-up PR to #12207 -- which will be relevant in a few seconds.
But TS is already in this state where very similar code, is A) well-typed, but B) will produce runtime errors. This comes down to how See two examples here, using Note: this was known way back in 2016, where
|
Both object keys and values are already unsound like this: const x = {
name: 'test',
0: true,
}
const y: { name: string } = x
const unsound: Record<string, string> = y So if the above is possible, wouldn't it be consistent for |
@probablykasper This example only shows that |
Ah ok, thank you for explaining |
Any solutions provided so far? |
If adding an extra package (maybe already been installed) to your prject is not a problem, you can have a solution with rambda import { toPairs } from "rambda"
toPairs({a: 1, b: 2, c: 3}); //=> [['a', 1], ['b', 2], ['c', 3]] All Keys types are have been stored! Docs: https://ramdajs.com/docs/#toPairs https://github.com/ramda/ramda/blob/v0.29.0/source/toPairsIn.js |
ts-extras is also a great option with it's Lesser known, Froebel is a lodash like library that is written from the ground up to have excellent typing. |
An argument against the assertion that "consistency dictates that Object.entries and Object.values have similar type behavior with the behavior of Object.keys", can be made as follows -
In short, key types and value types are different animals with different needs. |
Search Terms:
Object.values Object.entries sound soundness unsound inconsistent Object.keys
Code
Proposed change: MicahZoltu@603c363
Related Issues:
#12207
#12253
Back in November 2016, a PR (#12207) was submitted to make the types of
Object.entries
andObject.values
generic. This PR was reviewed and accepted, and as a note it was recommended thatObject.keys
be updated similarly. The author then submitted a PR to updateObject.keys
(#12253) but @ahejlsberg made the very valid point that this change was unsound and the PR was closed. The author of both PRs then suggested that perhaps #12207 should be reverted, but this revert never happened.This issue is to discuss options for rectifying this situation and I propose we make
Object.entries
andObject.values
consistent with the sound behavior ofObject.keys
. The major issue here is that this IS a breaking change since people may be relying on the currently unsound behavior ofObject.values
andObject.entries
. However, I think having TypeScript be inconsistent on this front indefinitely is not good, and I don't think changingObject.keys
to be unsound is the right solution, so at the least this change should be merged into TS 4.x.The text was updated successfully, but these errors were encountered: