Import approach
If you may be working in a monorepo, or are taking a 10-year perspective, you might prefer an import based approach (importing is pretty easy inside vscode anyway, literally just hit <tab>
)
Just put this somewhere and then type ObjectT
hit <tab>
and it should get auto-imported when using vscode
type KeyOf<T> = Extract<keyof T, string>
type ValueOf<T> = T[KeyOf<T>]
/**
* Nicely typed aliases for some `Object` Methods
* - PSA: Don't mutate `yourObject`s
* - Numerical keys are BAD `{ 1: 'ha!' }` may not appear in your resulting types
* - Discussion: https://stackoverflow.com/a/65117465/565877
*/
export const ObjectTyped = {
/**
* Object.keys, but with nice typing (`Array<keyof T>`)
*/
keys: Object.keys as <T extends {}>(yourObject: T) => Array<KeyOf<T>>,
/**
* Object.values, but with nice typing
*/
values: Object.values as <T extends {}>(yourObject: T) => Array<ValueOf<T>>,
/**
* Object.entries, but with nice typing
*/
entries: Object.entries as <T extends {}>(yourObject: T) => Array<[KeyOf<T>, ValueOf<T>]>,
/**
* Object.fromEntries, but with nice typing
*/
fromEntries: Object.fromEntries as <K extends string, V>(
yourObjectEntries: Array<[K, V]>
) => Record<K, V>,
}
Globals
If you prefer globals, add this to you src/index.ts
:
[ Sorry removed code sample for simplicity+ease of maintenance. Don't use globals. Or, see answer history. ]
Here's what you'll see when hovering over TypedObject
(Renamed to ObjectTyped
since it's easier to discover. If you type Object
then you can potentially notice ObjectTyped
)
And .entries
:
And yourEntries
:
Both approaches avoid mutating Object.keys
behavior in any way across your whole codebase. It's an explicit opt-in. The javascript defining the aliases is right next to the TS declaration.
Of course, developers will need to discover ObjectTyped.keys
, but, if it's used all over a codebase, it shouldn't take long to discover it.
======
Both approaches eschew using Extract<keyof T, string>
, since these are meant to be used across a whole codebase, it might not be the safest default (You might want to know if you have non-string keys, and handle that on a case by case basis, or address the root cause of non-string keys)
I think this is un-necessary precaution that just adds complexity. See comments below BenCarp's answer. Just, don't write this odd code:
{
1: 'asdf'
}
Due to Extract<keyof T, string>
typescript will hide this numerical key from you. But, if you know anything about javascript, you may know that Object.keys
will convert 1
into a string "1"
. Ideally typescript just had better default typing for Object.keys but sometimes you can't have your cake and eat it to.
If you just want to always see string keys, you can change Array<keyof T>
to Array<Extract<keyof T, string>>
, and you should still be fine most of the time.
Yeah, we're just doing this nice+obvious thing now.
(Object.keys(v) as Array<keyof typeof v>)
the definition is what it is – Titian Cernicova-Dragomir