Jon Lamb
Jon Lamb

Reputation: 1473

Define A Typescript Interface Where A Property Is An Existing Object's Properties

What is the best way to define an interface where one of the properties could be any of the properties on an existing object. In this case I have an object called fruits where I am simply storing a number of string constants and I want to define an interface (groceries) such that one of the properties (fruit) could be any of the string constants in the fruits object.

type Fruits = 'APPLE' | 'BANANA' | 'ORANGE';

const fruits = {
    APPLE: 'APPLE',
    BANANA: 'BANANA',
    ORANGE: 'ORANGE'
}

interface IGroceries {
    fruit: Fruits,
    vegetables: string[]
}

const initialGroceriesState: IGroceries = {
    fruit: fruits.APPLE,
    vegetables: []
}

This is my best attempt but this will throw an error saying:

Type '{ fruit: string; vegetables: never[]; }' is not assignable to type 'IGroceries'.
  Types of property 'fruit' are incompatible.
    Type 'string' is not assignable to type 'Fruits'.

Upvotes: 0

Views: 273

Answers (2)

Estus Flask
Estus Flask

Reputation: 222309

fruits key values are inferred to strings. This results in error, because Fruits is assignable to string type but not vice versa.

In order to avoid this, fruits type should be specified explicitly:

const fruits: { [K in Fruits]: K } = {
    APPLE: 'APPLE',
    BANANA: 'BANANA',
    ORANGE: 'ORANGE'
};

An another way is to use string enum, as another answer suggests. Depending on the case, this may or may not be desirable.

Upvotes: 0

Aleksey L.
Aleksey L.

Reputation: 37918

One of the options is switching to string enum:

enum Fruits {
    APPLE = 'APPLE',
    BANANA = 'BANANA',
    ORANGE = 'ORANGE'
}

interface IGroceries {
    fruit: Fruits,
    vegetables: string[]
}

const initialGroceriesState: IGroceries = {
    fruit: Fruits.APPLE,
    vegetables: []
}

Another option is correcting the typing of the existing object:

type Fruits = 'APPLE' | 'BANANA' | 'ORANGE';

const fruits: { [key: string]: Fruits } = {
    APPLE: 'APPLE',
    BANANA: 'BANANA',
    ORANGE: 'ORANGE'
}

interface IGroceries {
    fruit: Fruits,
    vegetables: string[]
}

const initialGroceriesState: IGroceries = {
    fruit: fruits.APPLE,
    vegetables: []
}

Upvotes: 3

Related Questions