0

I want to update an object of type IMessage with a new object with the same type

interface IMessage {
  id: string,
  text: string,
  // and many more attributes
}

const message: IMessage = {
  id: "_id",
  text: "old text",
  // and many more attributes
}

const newMessage: IMessage = {
  id: "_id",
  text: "new text"
  // and many more attributes
}

I am expecting my message after updating will have value exactly the same with newMessage, but I cannot just reassign message object because message is declared as a const. This is what I have so far

const updateMessage = (message: any, newMessage: typeof message): void => {
    for (const attr in newMessage) {
        if (attr !== "id" && newMessage.hasOwnProperty(attr)) {
            message[attr] = newMessage[attr];
        }
    }
};

This function is working. But in this function, I have to declare (message: any, newMessage: typeof message), otherwise Typescript will throw a warning Element implicitly has an 'any' type because type 'IMessage' has no index signature. How can I make this function accept only arguments with type IMessage?

Tera Mind
  • 263
  • 4
  • 17
  • 2
    you can use spread operator `const updateMessage = { ...message, ...newMessage}` this will copy the properties of both objectcs in `updateMessage` it will override the properties of the first object with second – hawks Nov 15 '19 at 10:42
  • @ajuni880 I need to update attributes of `message`, not create a new object – Tera Mind Nov 15 '19 at 10:45
  • and you can't declare `message` with `let`? You will be able to reassign – hawks Nov 15 '19 at 10:48
  • @ajuni880 that's the point. There are many reasons why I can't just declare `message` as `let`. One of those is you will violate the immutable rule. That's why I decided to use Typescript as the first place – Tera Mind Nov 15 '19 at 10:55
  • ahh i understand. You can check this https://stackoverflow.com/questions/32968332/how-do-i-prevent-the-error-index-signature-of-object-type-implicitly-has-an-an – hawks Nov 15 '19 at 11:02

2 Answers2

2

What are your thoughts on using Object.assign()? It copies all own and enumerable properties into its first argument, exactly as you seem to be doing. There's a little hiccup with exempting id from that, but you can deal with that by using rest object destructring. Like this:

const updateMessage = (message: IMessage, newMessage: IMessage): void => {
    const { id, ...nm } = newMessage; // make nm from newMessage without id prop
    Object.assign(message, nm); // update message with nm
};

This should behave like your original updateMessage() function without type errors. Note that this isn't particularly type safe, since Object.assign()'s typing allows any arguments. You could make your own typing which ensures that subsequent arguments don't add any unexpected properties, like this:

const safeAssign: <T>(target: T, ...args: Partial<T>[]) => T = Object.assign;

const updateMessage = (message: IMessage, newMessage: IMessage): void => {
    const { id, ...nm } = newMessage;
    safeAssign(message, nm);
};

But it behaves the same either way at runtime.

Okay, hope that helps; good luck!

Link to code

jcalz
  • 264,269
  • 27
  • 359
  • 360
0

U can use TS Generic types for this.

function updateMessage <T>(message: T, newMessage: T): void {
    for (const attr in newMessage) {
        if (attr !== "id" && newMessage.hasOwnProperty(attr)) {
            message[attr] = newMessage[attr];
        }
    }
};

Then pass the type when calling the function.

updateMessage<IMessage>(message, newMessage)

TS documentation about Generics: https://www.typescriptlang.org/docs/handbook/generics.html

Kary
  • 229
  • 1
  • 8