Typescript - Update object properties dynamically with Object.entries
Asked Answered
B

4

10

I want to implement a function that loops over the properties of an object and applies updates to another object of the same type.

interface Car {
  tires: number;
  name: string;
}

function updateCar(car: Car, updates: Partial<Car>) {
  Object.entries(updates).forEach(([key, value]) => {
    car[key] = value;
  })
}

The issue here is that there is an error

No index signature with a parameter of type 'string' was found on type 'Car'

when casting the key to keyof Car it works, but as soon as using "noImplicitAny": true there is again an error

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

Is there a way to solve this issue in a type-safe matter. Casting the car to any would work but I'd like to avoid it.

Many thanks Bene

Buxtehude answered 25/8, 2020 at 9:42 Comment(3)
Partial<Car> would allow me to pass {tires: 4, name: undefined}. Do you want that?Andria
@AluanHaddad - the question is just targeted to the typing problem. I know that this method has some caveats and some other checks would be necessary but this is not the scope of my question :)Buxtehude
Yes but I am trying to point out that they are related. If you just want a way to do it that won't complain, use Object.assign(car, updates)Andria
D
17

This question inspired me to revisit a similar problem I ran into.

While not a direct analog of your problem (i.e. this is a pure function that returns a new object rather than modifying arguments supplied by the caller), here's an update function that retains type-safety by using object-spread instead of the Object.entries() function to do its work:

function updateFromPartial<T>(obj: T, updates: Partial<T>):T { 
    return {...obj, ...updates}; 
}
Dilapidation answered 25/8, 2020 at 10:1 Comment(3)
Note that this returns a new object rather than updating in-placeChivers
Great! I wasn't aware Partial exist :)Fibroid
Thank you for this, spent days without figuring something out.Variscite
S
4

That function already exists. Why not just use?

Object.assign(car, updates);

The Object.assign() method copies all enumerable own properties from one or more source objects to a target object. It returns the modified target object. MDN

Smoodge answered 8/9, 2022 at 12:29 Comment(1)
It seems like Object.assign is not type checked, which means you can affect anything to car. For example if you Car type has a color: string but you update object comes with {color: null} this line of code will not raise any error (eventhough it should).Allograph
C
3

This is about as typesafe as I could get it. The only thing left not properly typed is car[key]. This is clearly not a perfect solution.

interface Car {
  tires: number;
  name: string;
}

function updateCar(car: Car, updates: Partial<Car>) {
    for (const update in updates) {
        const key = update as keyof Car;
        (car[key] as any) = updates[key];
    }
}
Coexecutor answered 25/8, 2020 at 10:14 Comment(0)
S
0

You can use Object.assign() as suggested in this answer but add typing to achieve OPs goal:

function assignFromPartial<T extends {}>(target: T, ...sources: Partial<T>[]) {
  return Object.assign(target, ...sources) as T;
}
Squire answered 28/7, 2024 at 8:23 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.