TLDR; Deriving types from array not working as expected (Stackblitz to illustrate it all).
Trying to improve some parts of a codebase, I stumbled across several different arrays used to map info from our backend into human-readable strings - something like below:
export const MyMapping = [
{ dbLabel: 'something', screenLabel: 'Something User-friendly' },
...
];
As this exists in several places, but the "contract" of the array was not being enforced by some type, I went on and wrote this:
export type DbLabelMapper = Record<'dbLabel' | 'screenLabel', string>;
(actually the first version used an interface, but the idea was the same)
Then, as the database labels were being used as types in other parts of the codebase, but were wrongly used as string
s, I went on and did as follows:
export const MyMapping: Array<DbLabelMapper> = [
{ dbLabel: 'something', screenLabel: 'Something User-friendly' },
...
] as const;
export type MyMappingType = typeof MyMapping[number]['dbLabel'];
Typescript screamed at me because I shouldn't assign a readonly type ([...] as const
) to a mutable type (Array<DbLabelMapper>
). So, I corrected the mapping signature as follows:
export const MyMapping: Readonly<Array<DbLabelMapper>> = [
{ dbLabel: 'something', screenLabel: 'Something User-friendly' },
...
] as const;
export type MyMappingType = typeof MyMapping[number]['dbLabel'];
Now, after that, my MyMappingType
has no types whatsoever. According to my example, what I wanted was MyMappingType = 'something' | 'anotherThing' | '...'
. Am I doing something wrong here, or did I miss something?
CodePudding user response:
You gave MyMapping
an explicit type of Readonly<Array<DbLabelMapper>>
. Using typeof MyMapping
will return this exact type.
To fix this you will have to remove the type annotation and let TypeScript infer the type.
export const MyMapping = [
{ dbLabel: 'something', screenLabel: 'Something User-friendly' },
] as const
But you will lose the type safety you had before as you can assign any value to MyMapping
now.
TypeScript 4.9 will introduce the satisfies
operator to help with this situation.
export const MyMapping = [
{ dbLabel: 'something', screenLabel: 'Something User-friendly' },
] as const satisfies Readonly<Array<DbLabelMapper>>
export type MyMappingType = typeof MyMapping[number]['dbLabel'];
// ^? type MyMappingType = "something"