12

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

3
  • Partial<Car> would allow me to pass {tires: 4, name: undefined}. Do you want that? Commented Aug 25, 2020 at 12:20
  • @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 :) Commented Aug 25, 2020 at 12:24
  • 1
    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) Commented Aug 25, 2020 at 12:27

4 Answers 4

18

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}; 
}
Sign up to request clarification or add additional context in comments.

3 Comments

Note that this returns a new object rather than updating in-place
Great! I wasn't aware Partial exist :)
Thank you for this, spent days without figuring something out.
4

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];
    }
}

Comments

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

1 Comment

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).
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;
}

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.