Home > Back-end >  Get the keys of an already typed object as types in typescript
Get the keys of an already typed object as types in typescript

Time:04-07

const obj1 = {
    foo: 'bar',
    goo: 'car',
    hoo: 'dar',
    ioo: 'far'
} as const

const obj2 = {
    koo: 'gar',
    loo: 'har',
    moo: 'jar',
    noo: 'kar'
} as const

type KeysOfObj1 = keyof typeof obj1  // "foo" | "goo" | "hoo" | "ioo"
type ValuesOfObj1 = typeof obj1[KeysOfObj1]  // "bar" | "car" | "dar" | "far"
type KeysOfObj2 = keyof typeof obj2  // "koo" | "loo" | "moo" | "noo"
type ValuesOfObj2 = typeof obj2[KeysOfObj2]  // "gar" | "har" | "jar" | "kar"

type OtherObj = Record<string, ValuesOfObj1 | ValuesOfObj2>

const obj3: OtherObj = {
    hello: obj1.foo,
    world: obj2.koo
} as const

const obj4: OtherObj = {
    hi: obj1.hoo,
    anotherWorld: obj2.moo
} as const

type KeysOfObj3 = keyof typeof obj3  // string
type KeysOfObj4 = keyof typeof obj4  // string

so I think the code explains itself. My question is on the last two lines, how can I get keys of obj3 and obj4 as type now I am getting string as those types but I want to get these:

type KeysOfObj3 = keyof typeof obj3  // "hellow", "world"
type KeysOfObj4 = keyof typeof obj4  // "hi", "anotherWorld"

CodePudding user response:

It is because of the line type OtherObj = Record<string, ValuesOfObj1 | ValuesOfObj2>, you are explicitly typing the key to type of string.

So when you do keyof typeof obj3, it looks at Record<string, ...> and uses that.

If you want to add typechecking, you'll either have to use a type alias with generics or a function with generics

type OtherObj<T extends string> = Record<T, ValuesOfObj1 | ValuesOfObj2>

// Define keys explicitly
const obj3: OtherObj<'hello' | 'world'> = {
    hello: obj1.foo,
    world: obj2.koo
} as const

Otherwise, if you don't like the redundant verbosity, you can sacrifice negligible (depending on who you ask) runtime code. This using the inferencing from type parameters to infer the return value.

const createObj = <K extends string>(
  obj: Record<K, ValuesOfObj1 | ValuesOfObj2>
) => obj

const obj4 = createObj({
    hi: obj1.hoo,
    anotherWorld: obj2.moo,
    foo: 'foobar' //Error thrown! <-- this is invalid value
} as const)

See this on TS Playground

  • Related