0

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 ?

  • Letting typescript infer the type is not weak typing at all. In fact, type inference is the **only** way you'll be able to do something like this (without manually entering the keys, that is). If you wanted to force the object values to be of a certain type, you could try something with a function that infers the key type but forces the value type, or a second variable that takes on the value of the first variable, uses the inferred keytype, and forces a value type. – Aplet123 Dec 11 '20 at 21:53
  • @Aplet123 I guess I confused type inference with weak typing. – Thomas Gauthier-Caron Dec 11 '20 at 21:56

2 Answers2

0

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
})
catchergeese
  • 664
  • 5
  • 9
  • I prefer to use infered typing when I can but the purpose of typing my object with FileTypeList in this case is to make sure that the values in the object are of type FileType. But it seems I can maybe use a function to do this as @Aplet123 suggested. – Thomas Gauthier-Caron Dec 11 '20 at 22:48
  • I see. Please have a look at edited answer. – catchergeese Dec 11 '20 at 23:03
0

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