Home > Back-end >  I need to update an array of objects with key/value pair only if it does not exists already
I need to update an array of objects with key/value pair only if it does not exists already

Time:08-04

I want to sort through two arrays and determine if any of the objects contain the same key/value pair. Where there is a match by id, I want to update the tempSearch array object to include the shelf key/value pair from the matched booksOnShelf object. If no match between the arrays, I want to update that tempSearch object with shelf: "none".

I am certain that both arrays have data. Currently, all shelf data returns as "none".

UPDATE (Including array data example.)

tempSearch = [{id: 1}, {id: 2}, {id: 3}, {id: 4}];
booksOnShelf = [{id: 1, shelf: "read"}, {id: 4, shelf: "wantToRead"}];

The final result of tempSearch:

console.log(tempSearch);
// [{id: 1, shelf: "read"}, {id: 2, shelf: "none"}, {id: 3, shelf: "none"}, {id: 4, shelf: "wantToRead"}];

END OF UPDATE

const mergeBooks = () => {
      let tempSearch = searchInventory;
      let booksOnShelf = bookInventory;

      tempSearch.forEach((searchBook, index) => {
        booksOnShelf.forEach(book => {
          if (searchBook.id === book.id) {
            searchBook.shelf = book.shelf;
          } else {
            searchBook.shelf = "none";
          }
        })
      })
      setMergedInventory(tempSearch);
    }

CodePudding user response:

You're really close, but the issue is that you're assigning to shelf in every iteration of the inner loop. Instead, find the book, then if you found it, use it, and if not, assign "none":

const mergeBooks = () => {
    let tempSearch = searchInventory;
    let booksOnShelf = bookInventory;

    tempSearch.forEach((searchBook) => {
        const book = booksOnShelf.find((book) => book.id === searchBook.id);
        searchBook.shelf = book && book.shelf ? book.shelf : "none";
    });
    setMergedInventory(tempSearch);
};

const searchInventory = [{id: 1}, {id: 2}, {id: 3}, {id: 4}];
const bookInventory = [{id: 1, shelf: "read"}, {id: 2}, {id: 3}, {id: 4, shelf: "wantToRead"}];

const mergeBooks = () => {
    let tempSearch = searchInventory;
    let booksOnShelf = bookInventory;

    tempSearch.forEach((searchBook) => {
        const book = booksOnShelf.find((book) => book.id === searchBook.id);
        searchBook.shelf = book && book.shelf ? book.shelf : "none";
    });
    // setMergedInventory(tempSearch);
    console.log(tempSearch);
};

mergeBooks();
.as-console-wrapper {
    max-height: 100% !important;
}

That said, a couple of notes:

  1. These days, I'd use a for-of loop instead of the forEach method.

  2. There doesn't seem to be any reason for the tempSearch / booksOnShelf variables, they just refer to the same things that serachInventory and bookInventory refer to, so you can just remove them.

So with those changes:

const mergeBooks = () => {
    for (const searchBook of searchInventory) {
        const book = bookInventory.find((book) => book.id === searchBook.id);
        searchBook.shelf = book ? book.shelf : "none";
    }
    setMergedInventory(searchInventory);
};

const searchInventory = [{id: 1}, {id: 2}, {id: 3}, {id: 4}];
const bookInventory = [{id: 1, shelf: "read"}, {id: 2}, {id: 3}, {id: 4, shelf: "wantToRead"}];

const mergeBooks = () => {
    for (const searchBook of searchInventory) {
        const book = bookInventory.find((book) => book.id === searchBook.id);
        searchBook.shelf = book && book.shelf ? book.shelf : "none";
    }
    // setMergedInventory(searchInventory);
    console.log(searchInventory);
};

mergeBooks();
.as-console-wrapper {
    max-height: 100% !important;
}

You could also replace the conditional operator (? :) with optional chaining and nullish coalescing:

const mergeBooks = () => {
    for (const searchBook of searchInventory) {
        const book = bookInventory.find((book) => book.id === searchBook.id);
        searchBook.shelf = book?.shelf ?? "none";
    }
    setMergedInventory(searchInventory);
};

const searchInventory = [{id: 1}, {id: 2}, {id: 3}, {id: 4}];
const bookInventory = [{id: 1, shelf: "read"}, {id: 2}, {id: 3}, {id: 4, shelf: "wantToRead"}];

const mergeBooks = () => {
    for (const searchBook of searchInventory) {
        const book = bookInventory.find((book) => book.id === searchBook.id);
        searchBook.shelf = book?.shelf ?? "none";
    }
    // setMergedInventory(searchInventory);
    console.log(searchInventory);
};

mergeBooks();
.as-console-wrapper {
    max-height: 100% !important;
}


If you're a fan of really condensed code (I'm not), that means you don't need the book constant anymore:

const mergeBooks = () => {
    for (const searchBook of searchInventory) {
        searchBook.shelf = bookInventory.find((book) => book.id === searchBook.id)?.shelf ?? "none";
    }
    setMergedInventory(searchInventory);
};

const searchInventory = [{id: 1}, {id: 2}, {id: 3}, {id: 4}];
const bookInventory = [{id: 1, shelf: "read"}, {id: 2}, {id: 3}, {id: 4, shelf: "wantToRead"}];

const mergeBooks = () => {
    for (const searchBook of searchInventory) {
        searchBook.shelf = bookInventory.find((book) => book.id === searchBook.id)?.shelf ?? "none";
    }
    // setMergedInventory(searchInventory);
    console.log(searchInventory);
};

mergeBooks();
.as-console-wrapper {
    max-height: 100% !important;
}


If bookInventory is a very large array, it's not ideal to search through it using a linear search for each book in searchInventory. Instead, you can create a Map of ids to books. Looking in a Map is a sublinear operation (meaning it takes less time, on average, than searching through one by one). Of course, you have the added overhead of building the Map, so it's a balance.

With that:

const mergeBooks = () => {
    // Build the Map
    const booksById = new Map(bookInventory.map((book) => [book.id, book]));
    // Use it in the process
    for (const searchBook of searchInventory) {
        const book = booksById.get(searchBook.id);
        searchBook.shelf = book?.shelf ?? "none";
    }
    setMergedInventory(searchInventory);
};

const searchInventory = [{id: 1}, {id: 2}, {id: 3}, {id: 4}];
const bookInventory = [{id: 1, shelf: "read"}, {id: 2}, {id: 3}, {id: 4, shelf: "wantToRead"}];

const mergeBooks = () => {
    // Build the Map
    const booksById = new Map(bookInventory.map((book) => [book.id, book]));
    // Use it in the process
    for (const searchBook of searchInventory) {
        const book = booksById.get(searchBook.id);
        searchBook.shelf = book?.shelf ?? "none";
    }
    // setMergedInventory(searchInventory);
    console.log(searchInventory);
};

mergeBooks();
.as-console-wrapper {
    max-height: 100% !important;
}

It might even be useful to maintain bookInventory as a Map rather than holding it as an array, if you regularly need to look up books in it by ID.

  • Related