ArH
ArH

Reputation: 13

How to set a variable for a key in typescript

Im trying to set bob via variable key;

interface Person {
  name:string,
  age:number
}
const bob:Person = {
  name:'bob',
  age:12
}
function setSome<T extends Person>(payload:Partial<T>){
  Object.keys(payload).forEach((key)=>{
    bob[key] = payload[key] // ts error
  })
}
// set age
setSome({
  age:13
})
// set name
setSome({
  name:'bob2'
})

But it doesn't work. What to do?

info:

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'Person'. No index signature with a parameter of type 'string' was found on type 'Person'.

Upvotes: 1

Views: 3203

Answers (2)

zzz
zzz

Reputation: 1105

The root cause of the ts error is that: Object.keys(payload) returns a string[] type, rather than (keyof T)[].

Typescript does that intentionally, because you can do something like this without any error:

interface Person {
  name:string,
  age:number
}

const person = {
    name:'bob',
    age:12,
    randomKey: 123, // <-- you can set any extra keys
}

function takePerson(person: Person) {
    return Object.keys(person);
}

takePerson(person); // <-- returns ["name", "age", "randomKey"]! 

More context around can be found: https://stackoverflow.com/a/55012175/9634568

I don't recommend to add the any index signature as other answers have suggested, unless you could set arbitrary keys at runtime. Because it will make your type check work unexpectedly, if you don't understand how it works. For example, a playground shows how getter and setter use cases cannot catch typos:

interface Person {
  name:string,
  age:number,
  [key: string]: any
}

const bob:Person = {
  name:'bob',
  age:12,
  agee: "typo"  // <- no ts error any more
}

bob.agee // <- no ts error any more

In your example, you can just simply do this as shown in this playground:

function setSome<T extends Person>(payload:Partial<T>){
  Object.assign(bob, payload);
}

// Or this, if you need to do more things before setting values
function setSome2<T extends Person>(payload:Partial<T>){
  for (let key in payload) {
    // do something here, e.g., filter key
    Object.assign(bob, { [key]: payload[key] });
  }
}

Upvotes: 0

Fatih Ersoy
Fatih Ersoy

Reputation: 739

It's because you are assigning a string key to the partial of your custom Person interface, but Typescript transpiler doesn't have the information that your Person interface are composed of string key and value pairs. To make this happen you should tell the transpiler that your Person object has string keys. So basically you can explicitly create keyvalue interface:

interface KeyValue{
    [key:string]:any 
}

Or you can use built-in Typescript types doesn't matter. The complete code is;

interface KeyValue{
    [key:string]:any
}

interface Person extends KeyValue{
  name:string,
  age:number
}

function setSome<T extends Person>(obj:T, payload:Partial<T>){    
    Object.keys(payload).forEach((key)=>{
       Object.assign(obj, {[key]: payload[key]});
    })

    return obj;
}

let bob:Person = {
  name:'bob',
  age:12
}

const returner = setSome(
  bob,
  {age:13},
)

console.log(returner);

typescript playgorund link

Upvotes: 1

Related Questions