1
interface Mapping {
  "x": (a: string) => void
  "y": (b: number) => void
}

interface In<T extends keyof Mapping> {
  readonly name: T,
  fn: Mapping[T]
}

const inHandlers: In<"x"> = {
  name: "x",
  fn(prop /* :string */) {}
}

someone knows how to get the typeof In["name"] as the index type of Mapping? so I don't have to write "x" two times

I already tried and somehow prop becomes any

interface In {
  readonly name: keyof Mapping,
  fn: Mapping[In["name"]]
}
const inHandlers: In<"x"> = {
  name: "x",
  fn(prop /* :any*/) {}
}
Le Val
  • 13
  • 1
  • 2

2 Answers2

1

If you know Mapping structure upfront, you can do it.

First of allm you need to use mapped types to create all possible/allowed data structures:

interface Mapping {
  "x": (a: string) => void
  "y": (b: number) => void
}


type Variants<Dictionary> = {
  [Prop in keyof Dictionary]: {
    name: Prop,
    fn: Dictionary[Prop]
  }
}

// type Result = {
//     x: {
//         name: "x";
//         fn: (a: string) => void;
//     };
//     y: {
//         name: "y";
//         fn: (b: number) => void;
//     };
// }
type Result = Variants<Mapping>

As you might have noticed, we are ended up with nested object where values represents allowed state. Now, we need to somehow obtain union of allowed values or in other words make a discriminated union type Consider this:

type Values<T> = T[keyof T]

interface Mapping {
  "x": (a: string) => void
  "y": (b: number) => void
}


type Variants<Dictionary> = {
  [Prop in keyof Dictionary]: {
    name: Prop,
    fn: Dictionary[Prop]
  }
}

// type Result = {
//     name: "x";
//     fn: (a: string) => void;
// } | {
//     name: "y";
//     fn: (b: number) => void;
// }

type Result = Values<Variants<Mapping>>

I have wrapped our result in Values utility type. This type returns a union of all object values without keys. This is actually what we want. We also can refactor it a bit:


type Values<T> = T[keyof T]

interface Mapping {
  "x": (a: string) => void
  "y": (b: number) => void
}


type Variants<Dictionary> = Values<{
  [Prop in keyof Dictionary]: {
    name: Prop,
    fn: Dictionary[Prop]
  }
}>


type Handlers = Variants<Mapping>

const x: Handlers = {
  name: "x",
  fn(prop /* :string */) { }
}

const y: Handlers = {
  name: "y",
  fn(prop /* :number */) { }
}

Playground

You don't need to use any extra generic arguments. Here you can find similar question and here you can find my article.

  • Very helpful it worked but is there a way to substract the `Parameters<>` of `Dictionary[Prop]`? I want to overwrite the first parameter and use the rest of the parameters of the given function `Dictionary[Prop]` (basically like binding a function type) – Le Val Jan 02 '22 at 17:58
  • @LeVal something like [this](https://tsplay.dev/N7KYGN) ? – captain-yossarian from Ukraine Jan 02 '22 at 18:17
0

You can use an identity function to constrain the parameter type:

TS Playground

function createMapping <T extends keyof Mapping>(mapping: In<T>): In<T> {
  return mapping;
}

const xMapping = createMapping({
  name: "x",
  fn (prop) {} // prop is string
}); // xMapping is In<"x">

const yMapping = createMapping({
  name: "y",
  fn (prop) {} // prop is number
}); // yMapping is In<"y">
jsejcksn
  • 27,667
  • 4
  • 38
  • 62
  • There is no other way to do it? like instead of writing `<"key">` to write `name: "key"` – Le Val Jan 02 '22 at 06:19
  • Unfortunately, no, you can't extract type information from an object that hasn't been defined yet in order to constrain its own shape. – jsejcksn Jan 02 '22 at 06:27
  • It is possible to make [discriminated union type](https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes-func.html#discriminated-unions) from `Mapping` – captain-yossarian from Ukraine Jan 02 '22 at 13:55
  • @captain-yossarian TIL the compiler will actually infer a more specific subtype of an annotation! – jsejcksn Jan 02 '22 at 21:05