1

The task is to rename interface keys. Nesting level can vary. For example:

interface Foo {
  _id: string
  nested: {
    _id: string
    nested: {
      _id: number
    }
  }
}

// how to create another type that looks like type below?

type Bar = {
  id: string
  nested: {
    id: string,
    nested: {
      id: number
    }
  }
}

So, in this example I want to rename all _id keys to id, but type should remain the same.

Andrew Vasylchuk
  • 4,671
  • 2
  • 12
  • 30

1 Answers1

2

You can try out the following type:

// T := your object type, K := key to change, R := new key name

type PickRename<T, K extends string, R extends string> =
  T extends object ? K extends keyof T ?
  ({ [P in R]: PickRename<T[K], K, R> } &
    { [P in Exclude<keyof T, K>]: PickRename<T[P], K, R> }) extends infer I ?
  { [PP in keyof I]: I[PP] } : never
  : T
  : T

Explanation using your example

  1. If T is an object and key K ("_id") is contained, process the object, otherwise we just return T.

  2. Drop the old property K (_"id") and add the renamed property R ("id"); process each property recursively further.

  3. The extends infer I ? { [PP in keyof I]: I[PP] } : never part is just for DX/UX/formatting purposes to make the intersected type more readable.

/*
type T1 = {
    id: string;
    nested: {
        id: string;
        nested: {
            id: number;
        };
    };
}
*/
type T1 = PickRename<Foo, "_id", "id">

To expand this to multiple properties, you can have a look at possible PickRename implementations here.

Playground

Community
  • 1
  • 1
ford04
  • 66,267
  • 20
  • 199
  • 171
  • Wooow! Thank a lot! That's fantastic! – Andrew Vasylchuk Jan 01 '20 at 17:22
  • I'm banging my head on this. I like the simplicity of this, but it doesn't work with nested arrays. I tried stuffing `T extends (infer U)[] ? PickRename[]` in there, but doesn't seem to work. Any sage wisdom would be appreciated! – Piittis Aug 24 '20 at 11:53
  • ^It works by replacing the second to last line with `: { [P in keyof T]: TransformType2 }`. Cheers. – Piittis Aug 24 '20 at 12:00
  • @Piittis you could do something like [this](https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgGIHt3IN4ChnID6wAJgFzIDOYUoA5vsiBNROcgNp4EHHvW0QDHkxaR2XRiL4UQAVwC2AI2hTkAXwC6jLbnW5cYAJ4AHFAAVgCANYAlCCDgKIAHgAqAGmQBpZBAAekCAklFQ09F62foEOIWGCdAB8yAC8yG7RQXFwIEYcmsgA-IzYnObIoMjWEEboMOmaFJY29o7O7hzmml7ekcnqyGSMGQFZoehKAFYQCGBFPpmxodW19RnFBAAUpZ0VIMi2jcjNdg5Orm4c3t0+fRrIAGRqO+WVAKL+CAA2ciSuK3V0j1EkcTq1zh0uj07uoAJSLYKhUAwaDIACSRRKZVe+wB9TRRzRnS691kEAAbqiKBlqQZjGZ0gBGVLHKynNquDDoLwAIj4PN5pB5iSAA). – ford04 Aug 24 '20 at 13:36
  • @ford04 I'm making some progress. That one breaks down if there is a object without _id in the middle of the path. I have bit simpler version, since I only need to replace $type -> type. But I'm having problems with some optional fields becoming required, Example link in next comment due to length limitation. – Piittis Aug 24 '20 at 15:34
  • @ford04 [example](https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgEJxBuyDeAoZZAGwhAHMwALAfgC5kBnMKUMvAXz1ElkRQEEADoJK4CyAO7AAJlTqNmrDnjxgAnoJQAxKAFdgYZAF5keAD64AJOs30A5OkyY77ZADI0WTOas2I9oREIF3dkQJIVbmh4JGQtAHt4sUJrDX8FFnJxGD0Deh19MAAacRAIJghpAHkAIwArenxCFL97PwBGOxLm5HjBMGB4zCJ5Jky2Qk5OPAgAD0F4qEM-ZAAVKAwGGEWAW1W0gB5VgD5jPFXkOcgQaQZe+ogEQ2pxO1TNO0vZ69vkAGsIGp4jA1shqMgABQ4ZCtNYAbTefjsAF1kK4PNC4QAFZCgZAAUVmCCIumkEAOAKBINWRWQiLSdmOyPo6022yge0Oq2xyNO7AAlMhaOJMTi8ZTgWtmWsNiAtrt9pojjy+XgWQBuFQrVbtYwytkKw4JeLHPAIIZMGHtFm6kxNZA5QqNcSEWEOLxwLriKZAA). Maybe my union types are wonky somehow. – Piittis Aug 24 '20 at 15:35
  • 1
    @Piittis we are drifting a bit away from the original question, so in short: there is a concept called [homomorphic mapped types](https://stackoverflow.com/questions/59790508/what-does-homomorphic-mapped-type-mean). If you want to preserve optional properties or `readonly`, you better keep them enabled: – ford04 Aug 24 '20 at 17:44
  • 1
    [PG](https://www.typescriptlang.org/play?#code/JYOwLgpgTgZghgYwgAgEJxBuyDeAoZZAGwhAHMwALAfgC5kBnMKUMvAXz1ElkRQEEADoJK4CyAO7AAJlTqNmrDnjxgAnoJQAxKAFdgYZAF5xAH1zIAJOs30A5OkyY7ydsgBkaLJjMXrGiHshEQgXN09gkhVuaHgkZC0Ae0SxQn9bBRZycRg9A3odfTAAGnEQCCYIaQB5ACMAK3p8QjSbQOQ7NoBGO1KW5ETBMGBEzCJ5Jiy2Qk5OPAgAD0FEqEM25AAVKAwGGBWAWw2AgB4NgD5jcQ3kRcgQaQYBhogEQ2pxO3TQm4W7h+QANYQNSJGCbZDUZAAChwyDa9A2AG1Pm07ABdVweCyIgAKyFAgOBoOQ1X2BlOxQ6XzsZzRCO2IF2ByOmlOuLRF3YAEpkLRxLDcfiQISQWCNnTNgymVBDickTiOa5xAiANwqdYbLrGSU7PYylkQY5JRJnPAIUZMOFdBFaoypOEBegAIi0AKI0jgDHqMCdfWQuSKTXEhHhHUcWF64nYlPKlRqDSD-VDTu6TqjHCAA) – ford04 Aug 24 '20 at 17:45