Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to type-enforce object value to match object key?

I have objects that implements types like this:

type TMyObject<T extends string> {
  category: T
}

I need to statically store them in an other object, and ensure that the key of this second object matches the value of the category field, like so:

const myObject: TMyObject<'foo'> = { category: 'foo' }

const dico = {
  foo: myObject, // good
  bar: myObject, // bad: 'bar' key does not match myObject.category
}

I encounter this case, because I have interfaces that extends IMyObject and fixes the category field to a precise value, like so:

type TMyFooObject = IMyObject<'foo'>

I spent two hours trying to create a type for the dico object that would work as described, but I just cannot figure a way to solve this ^^

Important note: The category field and the possibles types extending TMyObject are not static, we cannot use a "simple" union here...

As ever, a huge thanks for the time spent reading, and maybe answering to this question !

like image 277
Miaow Avatar asked Sep 06 '25 10:09

Miaow


1 Answers

We can make use of mapped types to define the values of an object based on their key.

type Dico<Keys extends string> = {
    [K in Keys]: TMyObject<K>
}

If we know all of the categories beforehand then we can create a map with those categories as keys.

const dico: Dico<'foo' | 'bar'> = {
  foo: { category: 'foo' }, // good
  bar: { category: 'foo' }, // error: Type '"foo"' is not assignable to type '"bar"'
}

You could use Partial<Dico<Category>> if you want to limit it to a set of valid categories but not require all of them to be present.

This makes sense only if your types are setup such that you already have an easy way to access a union type of all of the categories.

We want to be able to just look at an object and see if its keys match its category strings. We can do that, but only if we create the object through an identity function. A generic function allows us to infer the Keys type from the object.

const makeDico = <Keys extends string>(dico: Dico<Keys>) => dico;

const dico = makeDico({
  foo: { category: 'foo' }, // good
  bar: { category: 'foo' }, // error: Type '"foo"' is not assignable to type '"bar"'
});
// dico has type: Dico<"foo" | "bar">

Typescript Playground Link

like image 135
Linda Paiste Avatar answered Sep 09 '25 05:09

Linda Paiste