I want to create a function to avoid this:
Object.keys(object)
.map((key) => key as keyof typeof object) // appease ts
.some((key) => ... // do something with object[key] without ts complaining about it...),
all these options seems to work fine:
function keys<Key extends string | symbol | number, O extends Record<Key, unknown>>
(object: O) { // OK
// function keys<O extends Object>(object: O) { // OK
// function keys<O extends {}>(object: O) { // OK
// function keys<O extends object>(object: O) { // OK
return Object.keys(object) as Array<keyof O>
}
In this article I found the following explanation:
It seems to me like the first option (O extends Record<Key, unknown>
) is more accurate, but on the other hand Object.keys
is types like this in Typescript: (method) ObjectConstructor.keys(o: {}): string[]
So I'd like to know the differences and which one is better for representing any object
You really shouldn't try to make a generic version of Object.keys
. There's a good reason Object.keys
returns string[]
and not Array<keyof typeof whatever>
: the keys of an object may be altered at runtime, and because TS is structurally typed there can always be extra keys in the object beyond it's specified type, and those "extra" keys could point to anything.
It is not type-safe to do what you are doing, and it is disingenuous to callers of your keys function to pretend it is. Somebody might call your function, iterate through the array, pull object properties that per the type definition should be values of a certain type, and surprise! get blown up runtime. Probably in production, because some random guy named Murphy hates you.
But in terms of your actual question, check out this content from reddit:
Value is assignable to | {} | object | Record<string, unknown> |
---|---|---|---|
"string" | Yes | No | No |
true | Yes | No | No |
42 | Yes | No | No |
42n | Yes | No | No |
Symbol() | Yes | No | No |
null | No | No | No |
undefined | No | No | No |
() => {} | Yes | Yes | No |
[1, 2] | Yes | Yes | No |
[] | Yes | Yes | No |
{foo: "bar"} | Yes | Yes | Yes |
{} | Yes | Yes | Yes |
In essence:
You can assign anything to {}, except for null and undefined.
You can assign anything that's not a primitive to object. Primitives are strings, booleans, numbers, big integers, symbols, null and undefined.
You can only assign non-primitive non-function non-array objects to Record<string, unknown>.
Here's a TypeScript Playground demonstrating the table above.
In conclusion, trying to type out tables in markdown sucks. At least when I do it in HTML the LSP autocompletes it for me. TIL.