How to restrict object types based on a value in the object?

Viewed 74

For now I'm using this type alias:

export type Country = {
  name: 'USA' | 'Russia' | 'Bulgaria' | 'Romania' | 'Austria' | 'Great Britain' | 'Italy',
  code: 'us' | 'ru' | 'bg' | 'ro' | 'au' | 'gb' | 'it'
};

But it allows to create incorrect objects:

{ name: 'USA', code: 'ru'}

What is the best way to make a type for this purpose?

2 Answers

You could use a discriminated union approach:

type Country = {
    name: 'USA',
    code: 'us',
} | {
    name: 'Russia',
    code: 'ru',
} | {
    name: 'Bulgaria',
    code: 'bg',
} | {
    name: 'Romania',
    code: 'ro',
} | {
    name: 'Austria',
    code: 'au',
} | {
    name: 'Great Britain',
    code: 'gb',   
} | {
    name: 'Italy',
    code: 'it',   
};

// works
const myCountry: Country = {
    name: 'USA',
    code: 'us',
};

// does not
const myCountry2: Country = {
    name: 'USA',
    code: 'ru',
};

More reading here

You could also use a paramater map, but then you would have to specify the paramater on the interface:

type CountryMap = {
    USA: 'us',
    Russia: 'ru',
    Bulgaria: 'bg',
    Romania: 'ro',
    Austria: 'au',
    'Great Britain': 'gb',
    Italy: 'it',
};

interface ICountry<T extends keyof CountryMap> {
    name: T;
    code: CountryMap[T];
}

// works
const myICountry: ICountry<'USA'> = {
    name: 'USA',
    code: 'us'
};

// does not
const myICountry2: ICountry<'USA'> = {
    name: 'USA',
    code: 'ru',
};

Working typescript playground for both examples here

That isn't good to constuct such types manually. Provide single type definging courtry codes (you already got it) and accompany it with exported funciton of type CountryCode -> CountryName. You may want to require some other parametrs that define naming strategy (localization, short/full names, etc.).

Related