Reputation: 29909
I have an object like this (demo in ts playground):
const sites = {
stack: {url: 'https://stackoverflow.com/'},
google: {url: 'https://www.google.com/'},
azure: {url: 'https://portal.azure.com/'}
} as const
What I'd like to do is create a union type with all the keys that were used, which I can do like this:
type SiteNames = keyof typeof sites; // "stack" | "google" | "azure"
However, I'd also like to add type saftey to the sites
intialization where all of the object values are of a certain type like this (demo in ts playground):
interface ISiteDetails {
url: string;
}
const sites: Record<string, ISiteDetails> = {
stackoverflow: {url: 'https://stackoverflow.com/'},
google: {url: 'https://www.google.com/'},
azure: {url: 'https://portal.azure.com/'}
} as const
This provides some type checking when creating sites
, but also removes the const assertion from the final type, so now SiteNames just resolves to a string:
type SiteNames = keyof typeof sites; // string
Question: Is there any way to have both? Strong typing when creating a Record<any, ISiteDetails
but also the ability to extract all the object keys into a new union type?
Workaround: not as ergonomic, but I could add a final layer of type checking by reassigning site to an exported variable like this (demo in ts playground):
const SitesTyped: Record<SiteNames, ISiteDetails> = sites;
Upvotes: 2
Views: 947
Reputation: 10377
This is generally done through an identity function. This will allow you to constrain your inputs while still using their specific types like this (demo in ts playground):
function defineSites<T extends Record<string, ISiteDetails>>(template: T) {
return template;
}
const sites = defineSites({
stackoverflow: {url: 'https://stackoverflow.com/'},
google: {url: 'https://www.google.com/'},
azure: {url: 'https://portal.azure.com/'}
})
I have a little one-liner that I import sometimes. It's higher order identity.
export const HOI = <Constraint> () => <T extends Constraint> (definition: T) => definition;
export const defineSites = HOI<Record<string, ISiteDetails>>();
// use as normal
You may wish to write this as a function if you need it in a .tsx
file
export function HOI<Constraint>() {
return () => <T extends Constraint> (definition: T) => definition;
}
Upvotes: 2