Daisy Leigh Brenecki
Daisy Leigh Brenecki

Reputation: 7941

Can I use TypeScript to enforce an object's values depending on its keys?

I have the following types:

type Filter = {
  display: string
  id: string
  argType: 'string' | 'number' | 'date' | 'none'
}

type FilterMap = {[key: string]: Filter}

However, in this application, each string key in a FilterMap needs to match the id attribute of the corresponding Filter value.

So far, I've managed to get this to work:

type Filter<ID extends string> = {
  display: string
  id: ID
  argType: 'string' | 'number' | 'date' | 'none'
}

type FilterMap<IDS extends string> = {[ID in IDS]: Filter<ID>}

let x: FilterMap<'foo' | 'bar'> = {
  foo: {display: 'Foo', id: 'foo', argType: 'string'},
  bar: {display: 'Bar', id: 'bar', argType: 'string'},
}

This will produce errors if, for example, the second-last line instead read bar: {display: 'Bar', id: 'baz', argType: 'string'},, which is what I want!

Is there a way I can do this without having to type all of the keys out a third time, as I'm having to in the type argument to FilterMap here?

Upvotes: 3

Views: 408

Answers (1)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 250326

You can do this if you take advantage of the inference behavior of functions. We can create a function with a generic parameter to represent the union of string literal types and let the compiler figure out what that type is, which it will do.

type Filter<ID extends string> = {
    display: string
    id: ID
    argType: 'string' | 'number' | 'date' | 'none'
}

type FilterMap<IDS extends string> = { [ID in IDS]: Filter<ID> }


function createFilterMap<T extends string>(fm: FilterMap<T>) {
    return fm
}

// Will be of type FilterMap<"foo" | "bar">
let x = createFilterMap({ 
    foo: { display: 'Foo', id: 'foo', argType: 'string' },
    bar: { display: 'Bar', id: 'bar', argType: 'string' },
})

Upvotes: 2

Related Questions