Matty F
Matty F

Reputation: 3783

Replace types on some keys in a Typescript type

In the following code, I am trying to create a new type from an existing type, by looping through the keys and replacing only the ones that match the condition.

I am also using union types here.

class A {}

class B {
    constructor(public a: A, public n: number, public aa: A[]) {}
}

type X = A | B

type ReplaceKeyTypes<Type extends X, NewKeyType> = {
  [Key in keyof Type]: Key extends X ? NewKeyType : Type[Key]
}

const a: A = new A()
const b: B = new B(a, 1, [a, a])

const c: ReplaceKeyTypes<B, string> = {
    a: 'test',
    n: 2,
    aa: ['xyz']
}

This gives me the following errors in the last few lines of code:

My questions are:

  1. why does c.n get changed to a string, when the original key type is a number which does not satisfy "Key extends X"?
  2. how can I also apply the change to keys who are an array of the union type? In this example, c.aa should change from X[] to string[]

Upvotes: 1

Views: 5767

Answers (4)

undefined
undefined

Reputation: 6462

This type allows omitting and replacing in one step, if all keys you're replacing will have the same type:

type ReplaceKeys<Type, Keys extends string, NewType> = Omit<Type, Keys> & {
  [key in Keys]: NewType
}

// use:
type B = ReplaceKeys<A, 'k2' | 'k4', 'replaced'>

const b: B = {
  k1: 0,
  k2: 'replaced',
  k3: [],
  k4: 'replaced'
}

Or, if you want to overwrite multiple keys of various types at once:

type Overwrite<A, B> = Omit<A, keyof B> & B

// use:
type C = Overwrite<A, {k1: 'replaced', k2: 'also replaced'}>

const c: C = {
  k1: 'replaced',
  k2: 'also replaced',
  k3: [''],
  k4: true
}

Note that for simplicity, I did not make it require the new keys to exist on the original type.

Upvotes: 0

Omar Magdy
Omar Magdy

Reputation: 3303

type A = {
  a: string;
  b: string;
  c: string;
};

type B = Omit<A, "b" | "c">;

type C = Omit<A, "b" | "c"> & { b: number; c: number };

Now, if you hover over B you will see this:

type B = {
    a: string;
}

And type C has been this:

type C = {
  a: string;
  b: number; // Replaced
  c: number; // Replaced
};

Upvotes: -1

Jonathan Tuzman
Jonathan Tuzman

Reputation: 13289

I'm not sure this would work exactly as is with multiple keys you want to replace, but if you have just one key you want to "re-type" I've done:

type NewType = Omit<OldType, 'keyToChange'> & {
  keyToChange: NewTypeOfKey;
};

Haven't taken the time to genericize this but it would probably be pretty easy.

Upvotes: 3

Matty F
Matty F

Reputation: 3783

It seems when doing Key in keyof Type you get the literal key, i.e. the string that names the key. I had forgotten to get the key's value type by using Type[Key]. To fix, and to support the array case, here's what I came up with:

type ReplaceKeyTypes<Type extends X, NewKeyType> = {
  [Key in keyof Type]: Type[Key] extends X[]
    ? NewKeyType[]
    : Type[Key] extends X
    ? NewKeyType
    : Type[Key]
}

Upvotes: 3

Related Questions