typescript - Change the type of a single deeply nested type within an object - Stack Overflow

I have a type Example with a deeply nested type with a single property target I wish to change the type

I have a type Example with a deeply nested type with a single property target I wish to change the type of.

Each "level" has many additional properties with types which I do not want to change (not shown for brevity)

type Example = {
  level1: {
    level2: {
      level3: {
        target: string
      }
    }
  }
}

to

type Example = {
  level1: {
    level2: {
      level3: {
        target: "a" | "b"
      }
    }
  }
}

I tried using the answers of this question as a basis, but I am not confident with creating TypeScript functions so didn't get very far.

I also tried using some "deepOmit" functions I found across the internet, along with type merging, but I believe type merging with nested properties won't work - level1 and all its properties would be completely replaced, not merged;

deepOmit<Example, "target"> & { level1: { level2: { level3: { target: "a" | "b" }}}}

I think that I need to use a recursive TypeScript function to 'search' for the parameter name and replace its type with "a" | "b", but I am not having much success.

I have a type Example with a deeply nested type with a single property target I wish to change the type of.

Each "level" has many additional properties with types which I do not want to change (not shown for brevity)

type Example = {
  level1: {
    level2: {
      level3: {
        target: string
      }
    }
  }
}

to

type Example = {
  level1: {
    level2: {
      level3: {
        target: "a" | "b"
      }
    }
  }
}

I tried using the answers of this question as a basis, but I am not confident with creating TypeScript functions so didn't get very far.

I also tried using some "deepOmit" functions I found across the internet, along with type merging, but I believe type merging with nested properties won't work - level1 and all its properties would be completely replaced, not merged;

deepOmit<Example, "target"> & { level1: { level2: { level3: { target: "a" | "b" }}}}

I think that I need to use a recursive TypeScript function to 'search' for the parameter name and replace its type with "a" | "b", but I am not having much success.

Share Improve this question asked Mar 12 at 11:15 myolmyol 10k24 gold badges97 silver badges158 bronze badges 2
  • These sorts of deeply recursive utility types tend to have bizarre edge cases. You can use the approach from this playground link and it works for your example, but I have no idea if it works for your actual use cases. Please test thoroughly. If it works, then I can write up an answer explaining. If not, then please edit the post to include examples that demonstrate the failing use cases. – jcalz Commented Mar 12 at 12:29
  • From initial testing this appears to work in my use case. I understand the risks of edge cases for functions like this, but my specific use case has only one property uniquely named "target" so it appears to work as advertized – myol Commented Mar 12 at 13:33
Add a comment  | 

1 Answer 1

Reset to default 1

The simplest implementation I can think of looks like

type ReplaceNestedKey<T, K extends PropertyKey, V> =
    { [P in keyof T]: P extends K ? V : ReplaceNestedKey<T[P], K, V> };

where ReplaceNestedKey<T, K, V> takes an input type T, a key type K, and a value type V, and recursively replaces any property whose key is K with the property value type V. It uses a conditional type to check whether the current key P matches the key K to be replaced.

This might or might not display the type you expect with IntelliSense (you might just see ReplaceNestedKey in the displayed type), so I'll augment it like

type ReplaceNestedKey<T, K extends PropertyKey, V> =
    { [P in keyof T]: P extends K ? V : ReplaceNestedKey<T[P], K, V> }
    & ({} | undefined | null);

which intersects with an unknown-like type that conceptually changes nothing about the shape, but in practice forces TypeScript to evaluate the type more eagerly.

Now let's see it on your example:

type Example = { level1: { level2: { level3: { target: string } } } }

type Replaced = ReplaceNestedKey<Example, "target", "a" | "b">;
/* type Replaced = {
    level1: {
        level2: {
            level3: {
                target: "a" | "b";
            };
        };
    };
} */

That's what you wanted to see. So it works for your example code.

Still, any kind of recursive mapped type with conditional types will tend to have bizarre edge cases. It might not behave as expected for types with optional properties, index signatures, methods, unions, intersections, arrays, etc. And sometimes the refactoring necessary to address edge cases can be quite substantial (if it is even possible). So it's important, especially for future readers, to thoroughly test any such type against a wide range of important use cases.

Playground link to code

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744756379a4591906.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信