How use keyof on object shape rather than explicit type

993 views Asked by At

I want to create a type based on the keys of another object in TypeScript.

I have managed to do this with type inference. But if I use an explicit type Record<string, something> then keyof gives me string instead of a union of the actual keys I used.

TypeScript Playground GIF

Here's example code:

type FileType = "image" | "video" | "document";

type FileTypeList = Record<string, FileType>

const inferedFileList = {
    a: "image",
    b: "video",
    c: "document"
}

//type files = "a" | "b" | "c"
type files = keyof typeof inferedFileList;

const explicitelyTypedList : FileTypeList = {
    a: "image",
    b: "video",
    c: "document"
}

//type typedFiles = string
type typedFiles = keyof typeof explicitelyTypedList;

Relevant TypeScript Playground

Is there any way I can use explicit typing in the form Record<string, something> and still get a union type with keyof? I imagine it would be possible with a keyword that behaves like typeof but uses the shape of an object instead of its declared type, but does TypeScript implement such a thing ?

2

There are 2 answers

2
catchergeese On

Type inference can be really powerful in TypeScript and there is nothing wrong with not annotating types yourself.

When you explicitly type the object, you effectively type it less precisely:

const aString = "a" as const; // typeof aString is 'a'
const justString: string = aString; // typeof justString is 'string'

Maybe you need this type later in the process, so you can do something like:

type FileTypeRecord = Record<keyof typeof inferedFileList, FileType>

so get the whole Record type with just existing keys (a, b, c)

If you want to guard that object have valid values but also staticly known keys, you can try this approach:

type FileType = "image" | "video" | "document";

function getTypedFileObject<T extends { [k: string]: FileType }>(blueprint: T) : Record<keyof T, FileType> {
  return blueprint;
}

const obj = getTypedFileObject({
    a: "image",
    b: "video",
    c: "document",
    // d: "test" will not compile
})
0
captain-yossarian from Ukraine On

Maybe it will help you:

type FileType = "image" | "video" | "document";

const inferedFileList = {
    a: "image",
    b: "video",
    c: "document"
}

type FileTypeList = Record<keyof typeof inferedFileList, FileType>

const explicitelyTypedList : FileTypeList = {
    a: "image",
    b: "video",
    c: "document"
}

//type typedFiles = string
type typedFiles = keyof typeof explicitelyTypedList; // a | b | c