Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to avoid dynamic keyof object assign error in TypeScript

Let's say we have TypeScript code that looks like:

type User = {
  id: number,
  name: string,
}

let user1: User = {id: 123, name: "Hello"};
let user2: User = {id: 456, name: "World"};

let keys: (keyof User)[] = ["id", "name"];

for (let key of keys) {
  user1[key] = user2[key];
}

This gives error

Type 'string | number' is not assignable to type 'never'.

for the statement

user1[key] = user2[key];

If we change the definition of keys to

let keys: string[] = ["id", "name"];

the error goes away, but we lose type safety.

Is there some way we can avoid this error while still maintain type safety?

like image 293
Vaibhav K Avatar asked Nov 01 '19 08:11

Vaibhav K


Video Answer


1 Answers

There is no good way to avoid a type assertion here. In recent version on TS (post 3.5 I think) when writing through an index the value written has to be compatible with all possible property values specified by the key. In your case that would be number & string which reduces to never hence the error.

The root cause is that TS does not keep track of variables only of types, so as far as the types are concerned, your example would be no different from:

let key1 = 'id' as  keyof User;
let key2 = 'name' as  keyof User;
//Obvious error
user1[key1] = user2[key2] // same error, TS can't distingusih between this and your user1[key] = user2[key]

The simplest solution is to use a type assertion if, as in your case you are sure this is ok :

type User = {
  id: number,
  name: string,
}


let user1: User = { id: 123, name: "Hello" };
let user2: User = { id: 456, name: "World" };
for (let key of keys) {
  user1[key] = user2[key] as never
}

Play

Alternatively (but not any more type safe) you can use a small loophole where T[K] is assignable to index value:

type User = {
  id: number,
  name: string,
}


let user1: User = { id: 123, name: "Hello" };
let user2: User = { id: 456, name: "World" };

let keys: (keyof User)[] = ["id", "name"];

for (let key of keys) {
  set(user1, key, user2[key])
}

Play

like image 125
Titian Cernicova-Dragomir Avatar answered Oct 21 '22 11:10

Titian Cernicova-Dragomir