Skip to content

1367 - Remove Index Signature - Template literal alternative #3542

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
ianbunag opened this issue Sep 27, 2021 · 8 comments
Open

1367 - Remove Index Signature - Template literal alternative #3542

ianbunag opened this issue Sep 27, 2021 · 8 comments
Labels
1367 answer Share answers/solutions to a question en in English

Comments

@ianbunag
Copy link

type RemoveIndexSignature<Type> = {
  [
    Key in keyof Type
      as Key extends `${infer ConcreteKey}` ? ConcreteKey : never
  ]: Type[Key]
}

// Works with existing cases extended with:
type WithSymbol = {
  [key: symbol]: string,
  concrete: string
}

type Cases = [
  Expect<Equal< RemoveIndexSignature<WithSymbol>, { concrete: string }>>
]
@ianbunag ianbunag added answer Share answers/solutions to a question en in English labels Sep 27, 2021
@github-actions github-actions bot added the 1367 label Sep 27, 2021
@ssyrota
Copy link

ssyrota commented Dec 11, 2021

Can you help me, why it works?

@ianbunag
Copy link
Author

Sure. Let me break down what is happening here.

The whole syntax being used here is key remapping - also used in other solutions. It allows us to change or filter out keysets.

In the remap logic, we are determining which keys need to be filtered out via conditional typing:

as Key extends `${infer ConcreteKey}` ? ConcreteKey : never

We can phrase the logic here like "If the key being remapped is a string literal (or a specific string value instead of the generic string), retain the key (by returning the original key), otherwise, filter it out (by returning never, see documentation).

As for the condition:

Key extends `${infer ConcreteKey}`

We are trying to infer if the key can be pulled out of a template literal. From definition, template literals are built on string literal types, which would be the only types you can infer from it and not a generic string. Also, string can only extend the types string and any AFAIK, thus the condition resulting in false and filtering out generic types.

As to how TS handles these internally, I am not sure about those, the docs I have linked may be able to give insights about it. Hope these help.

@Psilocine
Copy link
Contributor

awesome! thanks a lot!

@tenkirin
Copy link
Contributor

Wow, the best explanation I've ever seen! Thanks!

@DerGernTod
Copy link

great explanation and thanks for the doc links!

@adoin
Copy link

adoin commented Apr 8, 2023

image

@webdevamit
Copy link

Hi @ianbunag could you please also explain why Key in keyof Type
as Key extends
has been used i really don't understand at index level why there is a requirement for as Key syntax, as we are already getting value for that using in keyof syntax.
the other question is sometime i have also seen that at argument level like
```
type RemoveIndexSignature<T, K extends keyof T> = {
[P in keyof T]: T[P]
}

could you also explain we are using keyof in 2 places in above example
      

@ianbunag
Copy link
Author

ianbunag commented Sep 2, 2023

Hey, my answer is outdated, I would recommend checking this answer as it passes all the latest test cases (even symbols which my answer previously supported).

why there is a requirement for as Key syntax, as we are already getting value for that using in keyof syntax

as Key is used to modify the type returned by keyof syntax. In my case I only wanted to get the keys that are string literals, anything generic will be filtered out.

Here is an example:

type DoNothing<Type extends Record<string, unknown>> = {
  [Key in keyof Type]: Type[Key]
}

type Base = {
  hello: string,
  hi: number,
}

/**
 * type AllFields = {
 *   hello: string,
 *   hi: number,
 * }
 */
type AllFields = DoNothing<Base>

type PickNumberFields<Type extends Record<string, unknown>> = {
  // Iterate over all keys (Key) of the parameter (Type)
  // Then check if the value associated with the current Key in Type is a number
  // If it is a number, retain, otherwise filter it out by remapping to never
  [Key in keyof Type as Type[Key] extends number ? Key : never]: Type[Key]
}

/**
 * type NumberFields = {
 *  hi: number;
 * }
 */
type NumberFields = PickNumberFields<Base>

Play with this in TypeScript playground.

See this comment for a thorough explanation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
1367 answer Share answers/solutions to a question en in English
Projects
None yet
Development

No branches or pull requests

7 participants