1
\$\begingroup\$

I've got an array of data from an API called content that needs to have its shape transformed as shown in the console output. Essentially, the objects within each formula array need to be pulled out and put into a formulas array but they need to carry along the role and id attributes of their parent object.

While the .forEach() with a nested .map() approach produces the desired output, I'm wondering if there's a better way to write this logic.

// data from API
const content = [
    { role: 'row', id: 0, expressions: { formula: [ { id: 'L34', name: 'CommodityName' }, { id: 'L4', name: 'ItemId' } ] } },
    { role: 'column', id: 1, expressions: { formula: [ { id:'LC', name: 'Year' }, { id: 'LA', name: 'Month' } ] } },
    { role: 'body',  id: 2, expressions: { formula: [ { id:'L1B', name: 'Demand' } ] } }
];

// logic to extract and flatten
const formulas = [];
content.forEach(o => {
    const data = o.expressions.formula.map((x) => ({ contentId: o.id, role: o.role, ...x }) );
    formulas.push(...data);
});

// look at results
const pretty = (data) => JSON.parse(JSON.stringify(data));
// console.log(pretty(content));
console.log(pretty(formulas));

\$\endgroup\$
5
  • 1
    \$\begingroup\$ Tiny bit of code, that seems to perform its task admirably. Not much we can add. \$\endgroup\$ Mar 18 at 18:34
  • 1
    \$\begingroup\$ I'm curious about possibilities to accomplish the same with Object.assign() or maybe .flatMap(). \$\endgroup\$
    – knot22
    Mar 18 at 18:35
  • 1
    \$\begingroup\$ No review just for you for your information: This can be done with flatMap: const formulas = content.flapMap(o => o.expressions.formula.map(...));. \$\endgroup\$
    – RoToRa
    Mar 18 at 18:39
  • \$\begingroup\$ Possibly, but there will be very little between these solutions. In the end there will be 2 loops, overt or hidden, going over the array, and generate the output. Let's see if someone picks up the gauntlet. Ah, I spoke too late. \$\endgroup\$ Mar 18 at 18:40
  • \$\begingroup\$ @RoToRa - Beautiful! Had to make 1 small change .flatMap instead of .flapMap. \$\endgroup\$
    – knot22
    Mar 18 at 18:47

2 Answers 2

1
\$\begingroup\$

You can try to do it with Array.reduce. It is better than original code because of 2 points:

  • We don't need to create an additional variable to show answer (newArr in my example is just for clear code, we can also not to use it and console.log the result directly)
  • In original code we use array.map method to save the "mutated" array in another variable and then destructing it so we have 2 loops. But we can do it with 1 loop, we just need to get each item in array and put it to another array without saving it anywhere

So I think that my example is less complex and faster.

P.S. I also think that we don't need pretty variable.

const content = [
    { role: 'row', id: 0, expressions: { formula: [ { id: 'L34', name: 'CommodityName' }, { id: 'L4', name: 'ItemId' } ] } },
    { role: 'column', id: 1, expressions: { formula: [ { id:'LC', name: 'Year' }, { id: 'LA', name: 'Month' } ] } },
    { role: 'body',  id: 2, expressions: { formula: [ { id:'L1B', name: 'Demand' } ] } }
];


const newArr = content.reduce((formulas, contentItem) => {
  contentItem.expressions.formula.forEach(formulaItem => formulas.push({
    contentId: contentItem.id,
    role: contentItem.role,
    ...formulaItem
  }));
  return formulas;
}, [])

console.log(newArr);

\$\endgroup\$
1
  • 1
    \$\begingroup\$ You have presented an alternative solution, but haven't reviewed the code. Please edit to show what aspects of the question code prompted you to write this version, and in what ways it's an improvement over the original. It may be worth (re-)reading How to Answer. \$\endgroup\$ Mar 21 at 10:56
1
\$\begingroup\$

The original version is clear, but you can avoid the unnecessary mutation and assign to formulas in a single statement using a flat map.

const formulas = content.flatMap(o =>
  o.expressions.formula.map(
    (x) => ({ contentId: o.id, role: o.role, ...x })
  )
)

For older versions of JS that don't have the flatMap built in, there is a flat map idiom using Array.concat:

[].concat(...x.map(f))

where x.map(f) returns a possible nested array. This will flatten the array 1 level, which is typically all you need.

In your case, this becomes:

const formulas = [].concat(
  ...content.map(o => o.expressions.formula.map(
    (x) => ({ contentId: o.id, role: o.role, ...x })
  ))
)
\$\endgroup\$
2
  • \$\begingroup\$ Your solution works. I was also able to get flatMap to work as follows: const formulas = content.flatMap(o => o.expressions.formula.map((x) => ({ contentId: o.id, role: o.role, ...x }) )); \$\endgroup\$
    – knot22
    Mar 24 at 15:31
  • \$\begingroup\$ Ah yes, that's better. I forgot they had added it to the standard library. \$\endgroup\$
    – Jonah
    Mar 24 at 16:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.