简体   繁体   中英

How to change array that one of the objects properties spread in to another in javascript

I'm trying to change an array that one of the objects properties spread in to another. IE create const result where the fruit items are added to the veg items. I'm able to isolate the fruit and veg types in to their own array by using .filter(item.type === 'veg' || item.type === 'fruit') but I'm unsure how to keep the first array as it is. I tried looking to do so using .reduce but not having any luck as I still need the first part of the data array.

Any help would be greatly received.

const data = [ { type: 'fish', weight: '2kg' }, { type: 'veg', items: ['carrot', 'cabbage', 'pea'] }, { type: 'fruit', items: ['apple', 'pear', 'orange' ]} ]
const result = [ { type: 'fish', weight: '2kg' }, { type: 'veg', items: ['carrot', 'cabbage', 'pea', 'apple', 'pear', 'orange'] } ]

These problems are easier to think of in small steps, and I like using functional programming for processing data. First, let's extract the fruits and veggies from the rest.

const produce = data.filter(entry => ["fruit", "veg"].includes(entry.type));
const remainder = data.filter(entry => !["fruit", "veg"].includes(entry.type));

Technically we could have built both at once with forEach instead of calling filter twice, but I'd say clarity beats efficiency here.

Next we can merge the fruit and veg items with the .reduce function and clever use of the ... spread notation.

const mergedItems = produce.reduce((acc, entry) => [...acc, ...entry.items], []);

Finally, we can merge these all into an new object using the spread notation again:

const result = [
  { type: "veg", items: mergedItems },
  ...remainder
];

There you have it. Here's the result:

 const data = [ { type: 'fish', weight: '2kg' }, { type: 'veg', items: ['carrot', 'cabbage', 'pea'] }, { type: 'fruit', items: ['apple', 'pear', 'orange' ]} ]; const produce = data.filter(entry => ["fruit", "veg"].includes(entry.type)); const remainder = data.filter(entry => !["fruit", "veg"].includes(entry.type)); const mergedItems = produce.reduce((acc, entry) => [...acc, ...entry.items], []); const result = [ { type: "veg", items: mergedItems }, ...remainder ]; console.log(result);


EDIT: Here are a few clarifications based on the comments.

Q: Is there away to keep a property from the original veg obj in the final result without hardcoding it?

If you don't mind modifying the original type="veg" entry, you could instead overwrite its items property only so that it includes fruits. But that question makes it sound like you know there's exactly one veg and one fruit entry, in which case the code above was complicated for nothing, and we could just use this:

 const data = [ { type: 'fish', weight: '2kg' }, { type: 'veg', price: "3.00", items: ['carrot', 'cabbage', 'pea'] }, { type: 'fruit', items: ['apple', 'pear', 'orange' ]} ]; const vegEntry = data.find(entry => entry.type === "veg"); const fruitIndex = data.findIndex(entry => entry.type === "fruit"); vegEntry.items = [...vegEntry.items, ...data[fruitIndex].items]; data.splice(fruitIndex, 1); console.log(data);

This adds the fruit items to the veg entry items and removes the fruit entry, therefore preserving all the properties of the veg entry.

If for some reason you don't want to modify the data in place and would rather create a new result object, you would change it to:

 const data = [ { type: 'fish', weight: '2kg' }, { type: 'veg', price: "3.00", items: ['carrot', 'cabbage', 'pea'] }, { type: 'fruit', items: ['apple', 'pear', 'orange' ]} ]; const vegIndex = data.findIndex(entry => entry.type === "veg"); const fruitIndex = data.findIndex(entry => entry.type === "fruit"); const newVegEntry = {...data[vegIndex]}; newVegEntry.items = [...newVegEntry.items, ...data[fruitIndex].items]; const result = [...data]; result[vegIndex] = newVegEntry; result.splice(fruitIndex, 1); console.log(result);

If this doesn't work in your browser it might be due to the {...data[vegIndex]} syntax which is a bit more modern. You might want to use Object.assign({}, data[vegIndex]} instead to create a shallow copy of the veg entry.

The logic can sound very convoluted, but its' actually quite simple when you break it down. What you want to do is:

  1. Use Array.prototype.reduce() and start with a blank array first. The blank array is the starting state of the accumulator.
  2. When you're at a current item (which is a nested object):
    • If it's type is not 'veg' or 'fruit', then you simply push the current item into the accumulator
    • If it is, then there are two possibilities:
      • The entry { type: 'veg', items: [...] } does not yet exist. Then, we simply create a new one, and assign the items to be the current entry's items to it, ie { type: 'veg', items: current.items
      • The entry actually already exists. Then, we use array spread to merge the items array

See proof-of-concept below:

 const data = [{ type: 'fish', weight: '2kg' }, { type: 'veg', items: ['carrot', 'cabbage', 'pea'] }, { type: 'fruit', items: ['apple', 'pear', 'orange'] }]; const result = data.reduce((acc, cur) => { // If type is not veg/fruit, we simply push it to the accumulator if (cur.type !== 'veg' && cur.type !== 'fruit') { acc.push(cur); } // Otherwise, we want to merge the items array else { // Find if the accumulator contains an object with type 'veg' const vegIndex = acc.findIndex(entry => entry.type === 'veg'); // If it doesn't exist, then we create a new object if (vegIndex === -1) { acc.push({ type: 'veg', items: cur.items }); } // Otherwise, we simply spread the array and merge it else { acc[vegIndex].items = [...acc[vegIndex].items, ...cur.items]; } } return acc; }, []); console.log(result);

If I understood your question, your concern is not to change the starting array as-is. In this case, you have to clone the starting array and then you do everything you want.

const data = [ 
   { type: 'fish', weight: '2kg' },
   { type: 'veg', items: ['carrot', 'cabbage', 'pea'] },
   { type: 'fruit', items: ['apple', 'pear', 'orange'] }
];
const result = [...data];

 result.find(x => x.type === 'veg').items = result.filter(x => x.type === 'veg' || 
 x.type === 'fruit').flatMap(x => x.items);
 result.pop();

 console.log(data, result);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM