Reputation: 13270
I have this object in my definition files
export const properties = {
name: '',
level: ['a', 'b', 'c'],
uri: '',
active: true
}
(note: it's not an interface it's actually a real object, the reason I need an object is because I need it as a reference during runtime)
Now I try to make a type from this object, so this is what I need
export type Properties = {
name: string;
level: 'a'|'b'|'c';
uri: string;
active: boolean
}
I tried using
export type Properties = typeof properties
but level
is translated as string[]
which is normal, but how can I use TypeScript to map ['a', 'b', 'c']
to 'a'|'b'|'c'
? And if it's possible how can I do that during the mapping of one type to another?
Thanks
Upvotes: 1
Views: 397
Reputation: 327819
If you define properties
exactly as you've done above, then it won't work. By the time you get around to writing type Properties =
, the compiler has already widened the level
property to string[]
, and forgotten all about the string literal types of its elements, as you've seen.
In order to even have a chance to get "a" | "b" | "c"
out of properties
, therefore, you will need to alter the definition of properties
. The easiest way is to use a const
assertion to give the compiler a hint that you want the narrowest type it can infer. For example:
const properties = {
name: '',
level: ['a', 'b', 'c'] as const,
uri: '',
active: true
}
We've asserted level
as const
, and now typeof properties
looks like this:
/* const properties: {
name: string;
level: readonly ["a", "b", "c"];
uri: string;
active: boolean;
} */
So, how can we transform that to get Properties
? Assuming the question "how can I do that during the mapping of one type to another?" means you'd like every array-like thing to be transformed to its element type, and assuming that you only need to do this one-level deep (and not recursively), then you can define this type function:
type Transform<T> = { [K in keyof T]:
T[K] extends readonly any[] ? T[K][number] : T[K]
}
That's a mapped type where we take the input type T
, and for each property key K
, we index into it to get its property type (T[K]
). If that property type is not an array (readonly any[]
is actually more general than any[]
), we leave it alone. If it is an array, then we grab its element type by indexing into it with number
(if you have an array arr
and a number n
, then arr[n]
will be an element).
For typeof properties
, that results in:
type Properties = Transform<typeof properties>
/* type Properties = {
name: string;
level: "a" | "b" | "c";
uri: string;
active: boolean;
} */
as desired.
Upvotes: 2