TypeScript
TypeScript copied to clipboard
Unnecessary any returned from Object.values and Object.entries
⚙ Compilation target
es6
⚙ Library
es2017.object
Missing / Incorrect Definition
This is a duplicate of #26010 which I believe can now be fixed. Object.entries and Object.values can now return the correct types without impacting enums.
https://www.typescriptlang.org/play/?target=99&jsx=0&exactOptionalPropertyTypes=true&lib=lib.esnext.d.ts&ts=5.5.0-dev.20240429#code/PTAEAEBcEMCcHMCmkBcpEGcB2iAekAoECAGwEsAjNTHfAgxLAVwFtQANUAbwEEAaUACEAvvQDGAeywZIoCSQAm7DKAC8oAPIUAVojGQAdADdoJJpgAU7AJQBuImFBOAegH4Ck6bNwr1Js5Y29sROoG70ZFiQiLAAZtBiiKAAktwETgDauGjMLBQxALpoMrCR8KAAPqDMJCT2oh5SMnJoqepcoACsaADk0TI9AgAcOUy1oML2ns3yCgBqapo6eob+5hgWEnYOoWHu07JGi2uWW8GOLu70xFgS0aCQABaYSZJjCqCmGBKg+aAYAAdyIcyNBPqAmFgyFJQJEZIhoB8JLE5EYYiQJIiVBQmLJYhJYKAAMSdIYAdgAzAAmAixSH6aFYJyMSClTAAHgAKgA+TZoDoZDDFVllIqgTm2CagaxoQUirDwAScgoZAqgWn0yCM5lRNkYLm8iT80CC0Z5QpoCVSmUmkplJUqtUarAMmHoXVkDk8vncW3C0oKiq5fKwMVW4TS2V2hUO1W+2DIJiwJlxhp0l1amEnfXeo2+03-eXwMOSiM2zmq52upnZg0+gVC6qsEMl62Wyvp6ugWu540F6PwIPNi3i0uR8VxjoJyBJlNq0RAA
Sample Code
interface I {
[x: number]: string | null;
}
const o: I = { 5: 'test', 8: null };
Object.values(o); // currently `any[]` should be `(string|null)[]`
Documentation Link
No response
This is working as intended.
This is perfectly legal code, and a return type of (string | null)[] would be wrong:
const o = { foo: 123, 5: 'test', 8: null };
const i: I = o;
See this FAQ entry: https://github.com/microsoft/TypeScript/wiki/FAQ#indirect-excess-properties-are-ok
Scratch that, I see the issue and it should really be returning unknown[]
I understand how that FAQ applies to Object.keys, but I do not think it applies here.
const o = { foo: 123, 5: 'test', 8: null };
const i: I = o;
Here you have explicitly type i: I so I would expect that Object.values would return I[string|number][] (edit: per fatCerebus's correction)
note that if you let i be inferred the result would be (string | number | null)[] which is also correct
Why would Object.values(i) return I[]? None of the properties are Is. The point Martin is making is that TS is only seeing the types here and there's no guarantee that a thing typed as I only has string | null values.
Scratch that, I see the issue and it should really be returning
unknown[]
Agreed, but that would be a very big breaking change for little gain.
Here you have explicitly type
i:
That was intentional to demonstrate the point. You don't know where and how the object was created.
@typeholes Just to be clear: From the type checker's point of view there's no difference between the explicitly typed o in your example and the explicitly typed i in Martin's. Returning (string | null)[] when Object.values is called on an I wouldn't be sound.
Interestingly we have the exact same issue with a record, but do get the specific result type
type T = Record<number, string|null>
interface I { [x: number]: string | null ; }
const o = { foo: 123, 5: 'test', 8: null };
const oT: T = o; const ovT = Object.values(oT); // (string | null)[]
const oI: I = o; const ovI = Object.values(oI); // any[]
https://www.typescriptlang.org/play/?target=99&jsx=0&exactOptionalPropertyTypes=true&lib=lib.esnext.d.ts&ts=5.5.0-dev.20240429#code/PTAEAEBcEMCcHMCmkBcpEGcB2iAekAoECAGwEsAjNTHfAgsrSRWAM2gGNFQBJUAb1ABtXGiwBXALYUWAXTQZIsRvFAAfUBJIlQAblABfUPUgBPAA7cAKqAC8oAEqIOAe1gATADwTpLADSgispY8GpaJAB89K5YiqAudgKgrC4uaACMAEwAzAEArGgA5MyKhQEAHGLi2oa6BDFxLlZoNvYu+g2Q8QBuraAA8hQAVs6QAHTd0CTimAAUTQCU+qDEs0Eq6prVJAtCskRgoEfHJ6enAHoA-PUusV0uPGh8bR23jd3PA8OjE1MzGPMeEsjsRoFhTHsDmdoTCrvQCEA
Object.values has an overload for array-likes and string index signatures (but not bare numeric index signatures).
values<T>(o: { [s: string]: T; } | ArrayLike<T>): T[];
Supporting arrays makes sense. I'm guessing the string index signature is meant for object-as-a-dictionary scenarios. Either one has a potential hazard with excess properties, though.
Correct, so my question is should we be inconsistent between the interface and record values. I think consistency would be better, especially when you consider that any is just explicitly unsound.
It's not really inconsistent though. Index signatures and interfaces represent different things.
Index signatures are "arrayish." They may not necessarily have numeric keys, but they represent collections of key/value pairs where the "element" values all conform to a single type, so it makes sense for Object.values to support this scenario.
By its very nature, an interface is not exhaustive. An interface represents a subset of an object's actual shape (or a commonality among otherwise differently-shaped objects). So even if Object.values returned a union of known property types based on the interface, you would still have to include unknown in the union to account for properties not identified by the interface. And if you include unknown in the union, then the entire thing reduces to unknown.
That it returns any instead of unknown is unfortunate, but it's due to historical reasons.
Implementation is in #58358
This issue has been marked as "Question" and has seen no recent activity. It has been automatically closed for house-keeping purposes.