20

I have this:

type Two = {
  one: number,
  two: string,
  three: boolean
}

I want it to create a type that would look like this:

type RenamedTwo = {
  one: number,
  two: string,
  four: boolean // difference
}

Tried to create it this way:

type Rename<T, K extends keyof T, N> = Pick<T, Exclude<keyof T, K>> & { [N]: T[K] }

In an attempt to use this way:

type Renamed = Rename<Two, 'three', 'four'>

But TSlint marks [N] as error and gives this error message:

[ts] A computed property name in a type literal must refer to an expression whose type is a literal type or a 'unique symbol' type. [ts] 'N' only refers to a type, but is being used as a value here.

1
  • In case someone wants to rename multiple properties, here is the type definition: type Rename<T, K extends keyof T, N extends string> = Pick<T, Exclude<keyof T, K>> & Record<N, valueof<Pick<T, K>>> Commented Oct 9, 2018 at 8:06

4 Answers 4

22

You need to use a mapped type for the renamed property as well:

type Two = {
    one: number,
    two: string,
    three: boolean
}


type Rename<T, K extends keyof T, N extends string> = Pick<T, Exclude<keyof T, K>> & { [P in N]: T[K] }

type Renamed = Rename<Two, 'three', 'four'>

Note that this will not work as expected if you provide more properties:

type Renamed = Rename<Two, 'two'  |'three' , 'four' | 'five'> // will be Pick<Two, "one"> & {
//    four: string | boolean;
//    five: string | boolean;
// }
Sign up to request clarification or add additional context in comments.

2 Comments

Thank you! Especially for the note. I adjusted the type definition based on your feedback: type Rename<T, K extends keyof T, N extends string> = Pick<T, Exclude<keyof T, K>> & Record<N, valueof<Pick<T, K>>>
Awesome. TIL you can use in operator in a string type or Typescript reads it as a union type even if it is of type string?
16

In the current typescript version 4.6.2, there is a remapping gramma to use. It can be implement this much easier.

PlayGround

type RenameByT<T, U> = {
  [K in keyof U as K extends keyof T
    ? T[K] extends string
      ? T[K]
      : never
    : K]: K extends keyof U ? U[K] : never;
};

type Two = { one: number; two: string; three: boolean };

// returnType = { one: number; two: string, four: boolean };
type renameOne = RenameByT<{three: 'four', five: 'nouse'}, Two>;

// returnType = { x: number, y: string, z: boolean; }
type renameAll = RenameByT<{one: 'x', two: 'y', three: 'z'}, Two>;

The RenameByT type can separate by several parts. The explain is for the example renameOne

  1. K in keyof U means all the key in U. For the example is one | two | three
  2. as clause since ts4.1 can use. but i can't use in 4.4.4 but can use in 4.6.2. This is use to rename Key K by the condiction type
  3. K extends keyof T. keyof T means three | five.
  4. T[K] extends string means T[K] is string. T['three'] is string so returns four, T['five'] returns nouse;
  5. the return type K extends keyof U, so T['three'] is satisfy so return { four: U['three'] } means { four: boolean}
  6. others keys return as origin

reference:

https://github.com/microsoft/TypeScript/issues/40833 https://www.typescriptlang.org/docs/handbook/2/mapped-types.html

1 Comment

Could or should it be keyof any rather than string? I.e. a shorthand for string | number | symbol.
3

It is perhaps worth mentioning that you can solve the OP's original problem without writing a generic type like Rename. If you just want to do it once, you could do something much simpler.

type RenamedTwo = { [Property in keyof Two as Property extends 'three' ? 'four': Property]:  Two[Property] }

Comments

0

I had a similar need but I wanted to be able to rename as many properties of an object that I wanted to so I came up with this function where I pass the object to rename and the associated mapping:

type Mapped<
  Type extends object,
  Mapping extends Partial<Record<keyof Type, string>>,
> = {
  [Property in keyof Type as Property extends keyof Mapping
    ? Mapping[Property] extends string
      ? Mapping[Property]
      : Property
    : Property]: Type[Property];
};

export function renameProperties<
  Type extends object,
  Mapping extends Partial<Record<keyof Type, string>>,
  Result extends Mapped<Type, Mapping>,
>(obj: Type, mapping: Mapping): Result {
  return Object.entries(obj).reduce<Result>((acc, [key, value]) => {
    const newKey = mapping[key] ?? key;
    return {
      ...acc,
      [newKey]: value,
    };
  }, {} as Result);
}

I used the remapping ability shared by @banana :)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.