Home > Software design >  Map function output type dynamically according to function parameter type
Map function output type dynamically according to function parameter type

Time:09-23

// Provided input
const input: Input = {
  hello: {
    type: "string",
  },
  first: {
    second: {
      type: "number"
    }
  }
}

// Expected output
const output = {
  hello: "some-string",
  first: {
    second: 42
  }
}

// Function that processes the input and spits out the expected output
function processInput<T extends Input>(input: T): Output<T> {
  // Some logic
}

I want to process the nested input object using the processInput function to generate an output that looks like the output object. This can be simply done by checking if the type property exists, etc.

But my problem is the writing a type Output for the output. I would like to precisely type the output according the the provided input.

This is what I came up with so far:

export type Property = { type: "string" } | { type: "number" };
export type Input = { [key: string]: Property | Input };

export type Output<T extends Input> = {
  [Key in keyof T]:
    T[Key] extends { type: "string" } ? string :
    T[Key] extends { type: "number" } ? number :
    T[Key] extends Input ? Output<T[Key]> :
    never
};

When accessing the hello property (for example, output.hello), it's always of type never. What's going wrong?

CodePudding user response:

If the input is dynamic, this is impossible.

TypeScript only runs at compile-time, so it can't infer which properties a dynamic object has, because that information doesn't exist yet.

If the object is static (unlikely in actually useful code), you can use captain-yossarian from Ukraine's solution:

export type Property = { type: "string" } | { type: "number" };
export type Input = { [key: string]: Property | Input };

export type Output<T extends Input> = {
  [Key in keyof T]:
  T[Key] extends { type: "string" } ? string :
  T[Key] extends { type: "number" } ? number :
  T[Key] extends Input ? Output<T[Key]> :
  never
};

function processInput<T extends Input>(input: T): Output<T>
function processInput<T extends Input>(input: T) {
  return null as any
}

const result = processInput({
  hello: {
    type: "string",
  },
  first: {
    second: {
      type: "number"
    }
  }
})
  • Related