TypeScript: Correctly handle recursive types

Viewed 74

Edit

I got a step further. The more complex example looks like this (Playground):

type DeepFunctions = {
  [key: string]: ((...args: any[]) => any) | DeepFunctions
}

function changeDeepFunctions<T extends DeepFunctions>(deepFunctions: T): T {
  const copy = {...deepFunctions} as DeepFunctions;

  for(let key in copy) {
    const value = deepFunctions[key];
    if(value instanceof Function) {
      copy[key] = ((...args) => {
        console.log("hi");
        return value(...args);
      });
    } else {
      copy[key] = changeDeepFunctions(value);
    }
  }

  return copy as T;
}

Everythings fine and the output is exactly like it should be. However, I don't get why I needed to do it this way. Maybe still someone can help me understand.

Original Post

I'm kind of new to typescript and I cannot find a good solution for a seemingly easy problem.

All Code you can see here is written down in this PlayGround.

Because my real example is a bit too heavy, I reduced it to these (hopefully :)) equal examples:

  1. I would like to write a function that gets an object with several functions and map it to an object with mapped functions. Kind of hard to describe, but easy to understand with code:
type Functions = {
  [key: string]: (...args: any[]) => any;
};

export function changeFunctions<T extends Functions>(functions: T): T {
  const copy = { ...functions };

  for (let key in functions) {
    const func = functions[key];
    copy[key] = ((...args) => {
      console.log("hi");
      return func(...args);
    });
  }

  return copy;
}

If you have look at the code in the playground, you can see the

copy[key] = ...

line produces a type error: Type '(...args: any[]) => any' is not assignable to type 'T[Extract<keyof T, string>]'.(2322) It's fixable by adding as typeof func at the end of the assignment, but that doesn't feel right.

Is there a clean way to write this?

The second example extends the first, so the solution might be similar:

  1. As an extension to 1., the function now should be able to correctly convert objects containing functions, but also recursively contain other objects with functions. Here is the code:
type DeepFunctions = {
  [key: string]: ((...args: any[]) => any) | DeepFunctions
}

function changeDeepFunctions<T extends DeepFunctions>(deepFunctions: T): T {
  const copy = {...deepFunctions};

  for(let key in deepFunctions) {
    const value = deepFunctions[key];
    if(value instanceof Function) {
      copy[key] = ((...args) => {
        console.log("hi");
        return value(...args);
      });
    } else {
      // it must be a DeepFunctions object
      copy[key] = changeDeepFunctions(value);
    }
  }

  return copy;
}

As you can see in the playground, the copy[key] assignment fails in the same way as in the example above. The other assignment:

copy[key] = changeDeepFunctions(value);

doesn't raise an error. The type is still wrong, though. Hovering over value shows a type of never.

Have you any hints how to correctly write such methods in typescript with correct types?

0 Answers
Related