Typescript interface for dynamic object

Viewed 166

Suppose I can have objects like this:

const filters = {
    name: { value: 'some-value', rank: 'some-value' },
    place: { value: 'some-value', population: 'some-value' },
}

and it could also be like this...

const filters = {
    name: { value: 'some-value', rank: 'some-value' },
    place: { value: 'some-value', population: 'some-value' },
    description: { value: 'some-value', translation: 'some-value' },
    popularity: { value: 'some-value', ranking: 'some-value' },
}

How do I create an interface for that (a dynamic object)?

2 Answers

One possibility would be to make description and popularity optional by adding a ?, like so:

Approach 1

interface Filters {
    name: object,
    place: object,
    description?: object,
    popularity?: object,
}

For more information on property modifiers, see the documentation

Another approach would be to use interface inheritance by using the keyword extends

Approach 2

interface ParentFilters {
    name: object,
    place: object
}

And for the derived interface

interface derivedFilters extends parentFilter {
   description: object,
   popularity: object,
}

It's not possible to create a type which forces to be { a } or { a, b, c }. This is called Discriminated Unions and you can read more about this problem here.

You can use booth approaches which @SimonRosengren mentioned. To force typesafety when defining an object you have to implicitly say which type is what:

interface FilterTypeBase {
    name: { value: string, rank: string },
    place: { value: string, population: string }
}

interface FilterTypeExtended extends FilterTypeBase {
    description: { value: string, translation: string },
    popularity: { value: string, ranking: string }
}

//  OK
const filters: FilterTypeBase = {
    name: { value: 'some-value', rank: 'some-value' },
    place: { value: 'some-value', population: 'some-value' },
}

// ❌ ERROR: Property 'popularity' is missing
const filters2: FilterTypeExtended = {
    name: { value: 'some-value', rank: 'some-value' },
    place: { value: 'some-value', population: 'some-value' },
    description:  { value: "string", translation: "string" }
}

If you use want to decide in a function which of booth variantes your type is. You can do this with narrowing:

type FilterType = FilterTypeBase | FilterTypeExtended 

function doSomething(filter: FilterType) {
    if('description' in filter) {
        // Here it's the extended type
    }
}

Typescript docs: Narrowing

Related