How can I define a type that maps keys to a generic of that key, e.g. Record<K, SomeGeneric<key>>?

I’m trying to do something that seems like it should be simple, but I’m at a total loss for how to accomplish it. I’ll try to summarise it succinctly here and elaborate on the actual use case below.

I want to create a Record style structure where the keys map to a value type of SomeGeneric<K>, where K is the key.

A straightforward way I’d initially try to write this is along the lines of Record<K, SomeGeneric<K>>, but that’s not a legal type definition: I get “Cannot find name ‘K’. ts(2304)”. Naturally this doesn’t conceptually work either on re-examination: I’m defining all the keys as type K, and SomeGeneric as corresponding to the key type. There’s definitely a more correct way to write this kind of type — but I can’t actually figure out what that is. Maybe it’s far more complex than I expected, or I’m completely overthinking it. How do I define this type?

The use case for this structure

The pragmatic reason I want to do this is I’m trying to create a diff between older/newer versions of objects. I’ll define a Sprocket interface like this:

interface Sprocket {
  name: string;
  fobbed: boolean;
  cycles: number;
}

const older: Sprocket = { name: 'Gizmo', fobbed: true, cycles: 12 };
const newer: Sprocket = { name: 'Gizmo', fobbed: false, cycles: 45 };

Now I want to create a diff between the older and newer versions of this sprocket: I want each key to correspond to a value of the form { previous: T, current: T }, where T is the value of that key. Essentially like this:

interface Sprocket {
  name: { previous: string, current: string };
  fobbed: { previous: boolean, current: boolean};
  cycles: { previous: number, current: number};
}

But that’s a lot of repetition and I’m essentially redefining the Sprocket interface. Instead I want to make a generic version:

interface Diff<T> {
  previous: T;
  current: T;
}

type SprocketValueType<K extends keyof Sprocket> = Sprocket[K];

interface SprocketChanges {
  name: Diff<SprocketValueType<'name'>>;
  fobbed: Diff<SprocketValueType<'fobbed'>>;
  cycles: Diff<SprocketValueType<'cycles'>>;
}

So far so good. SprocketValueType reliably gives me the type that corresponds to any given key:

enter image description here
enter image description here

And I can supply that to the Diff, so that SprocketChanges picks up the type for that property automatically:

enter image description here

But I’m still having to manually redefine the keys found in Sprocket. I’d rather define it generically with the Socket interface as the single source of truth on keys and types, meaning something like this:

type SprocketChanges = Record<K extends keyof Sprocket, Diff<SprocketValueType<K>>;

or this:

type SprocketChanges = Record<keyof Sprocket, Diff<SprocketValueType<key>>;

But neither of these are legal code. The second is even wishing for a magic value key that doesn’t exist. Both surface a ts(2304) error:

enter image description here

enter image description here

What type definition will work? How can I define this SprocketChanges interface, and map Sprocket’s keys to a Diff<T> with that key’s value?

>Solution :

The solution part type Changes<T> = {[K in keyof T] : Diff<T[K]>} means map a type that for every key the object has, create a property with a generic Diff<T> type that has T[K] as type argument which means typeof Object[Key]

interface Sprocket {
  name: string;
  fobbed: boolean;
  cycles: number;
}

interface Diff<T> {
  previous: T;
  current: T;
}


type Changes<T> = {[K in keyof T] : Diff<T[K]>}

type SproketChanges = Changes<Sprocket>

Playground

Leave a Reply