1

Hi everyone. :)

As a followup to this question a have the following code:

interface SubThing {
  name: string;
}

interface Thing {
  subThing: SubThing; // or any other property of type SubThing like in UpperThing
}

interface UpperThing {
  thing: Thing
}

interface ThingMapA {
  map: { thing: { subThing: { name : 'subThing name' } } };
}
interface ThingMapB {
  map: { thing: { otherThing: { name : 'subThing name' } } };
}

class Handler<T extends Record<keyof T, Record<keyof T, Thing>>> {}

const handler = new Handler<ThingMapA>(); // error
const handler = new Handler<ThingMapB>(); // error

I world like Handler to accept any class with properties (ideally at least one) of type UpperThing which has any property (ideally at least one) of type Thing. But ThingMap is not recognised. It leads to an error. Any suggestions?


This is another example based on the approach of @jcalz

interface SubThing {
  name: string;
}

interface Thing {
  subThing: SubThing;
}

interface UpperThing {
  thing: Thing
}

interface ThingMapA {
  map: { thing: { subThing: { name: 'subThing name' } } };
}

interface ThingMapB {
  map: { otherThing: { subThing: { name: 'subThing name' } } };
}

interface ThingMapC {
  map: { thing: { otherSubThing: { name: 'subThing name' } } };
}

interface ThingMapD {
  map: { otherThing: { otherSubThing: { name: 'subThing name' } } };
}

class Handler<T extends { [K in keyof T]: { [P in keyof T[K]]: Thing } }> { }

const handlerA = new Handler<ThingMapA>(); // okay
const handlerB = new Handler<ThingMapB>(); // okay
const handlerC = new Handler<ThingMapC>(); // error
const handlerD = new Handler<ThingMapD>(); // error

Kleywalker
  • 49
  • 5
  • Is this really a [mre]? Where is the `name` property mentioned anywhere in `ThingMap`? My guess is that you're looking for [this approach](https://tsplay.dev/mbv04m) but without an example that makes sense I can't be sure. – jcalz Apr 01 '23 at 14:43
  • It was not. Sorry for that. I corrected it. Your approach is my solution. Thanks! – Kleywalker Apr 01 '23 at 14:49
  • Sorry. Unfortunalty this does not quite do the job. I would like to change `subThing` to `otherThing` in `ThingMap` without having the need to change it in `Thing`. The name of this properties should be generic as well. Sorry again and thanks for your time! – Kleywalker Apr 01 '23 at 14:58
  • I don’t think I understand. Please [edit] the question to show enough examples to cover the cases you care about. – jcalz Apr 01 '23 at 15:03
  • I edited my example and added another one based on your approach. – Kleywalker Apr 01 '23 at 15:16
  • Like [this version](https://tsplay.dev/wXvKVm) then? Please test to make sure it meets your needs and let me know. – jcalz Apr 01 '23 at 16:20
  • This work. I see a pattern there. Thanks! – Kleywalker Apr 01 '23 at 16:50

1 Answers1

1

If you need the type T to be constrained so that every sub-sub-property is of type SubThing, then you will need to write a recursive constraint involving the appropriate depth of nested mapped types:

class Handler<T extends {
  [K in keyof T]: {
    [P in keyof T[K]]: {
      [Q in keyof T[K][P]]: SubThing
    }
  }
}> { }

Let's test it out:

const handlerA = new Handler<ThingMapA>(); // okay
const handlerB = new Handler<ThingMapB>(); // okay
const handlerC = new Handler<ThingMapC>(); // okay
const handlerD = new Handler<ThingMapD>(); // okay

const badhandler = new Handler<{
  a: { b: { c: { name: "" } } },
  d: { e: { f: { name: 123 } } } // error!
}>
//   The types of 'd.e.f.name' are incompatible between these types.

Looks good.

Playground link to code

jcalz
  • 264,269
  • 27
  • 359
  • 360