I have an array of objects like this below. As you can see, i have rows
nested inside input
and rows
is also array of objects.
let input = [
{
"title": "ENGINEERING",
"rows": [
{ "risk": "P1", "radarLink": "rdar://92642113" },
{ "risk": "P2", "radarLink": "rdar://92694095" },
{ "risk": "P3", "radarLink": "rdar://92694235" },
],
},
{
"title": "ENVIRONMENT",
"rows": [
{ "risk": "P1", "radarLink": "rdar://92684289" },
{ "risk": "P2", "radarLink": "rdar://92695531" },
{ "risk": "P5", "radarLink": "rdar://92424550" },
],
},
]
First of all, i want to filter rows
and only get those objects whose risk
=== 'P1'. Then i also want to create a new object named p2PlusRow
which will hold the count of objects whose value of risk
!== 'P1. This new object p2PlusRow
will gave count
as mentioned above. It also has a property named radarLink
. This is nothing but concatenation of radarLink whose risk !== 'P1'
For e.g. in ENGINEERING
, there are 2 objects whose risk is not equal to P1. the radar links of those are rdar://92694095
and rdar://92694235
. So the concatenated value would be rdar://92694095&92694235 (each radar ticket is appended as shown below
let output = [
{
"title": "ENGINEERING",
"rows": [
{ "risk": "P1", "radarLink": "rdar://92642113" },
],
"p2PlusRow": {
"count": "2",
"radarLink": "rdar://92694095&92694235"
}
},
{
"title": "ENVIRONMENT",
"rows": [
{ "risk": "P1", "radarLink": "rdar://92684289" },
],
"p2PlusRow": {
"count": "2",
"radarLink": "rdar://92695531&92424550"
}
},
]
In order to achieve this result, i tried a few things but i am stuck on getting the count
and radarLink
values dynamically. Below is the tried code
let output = input?.map((obj) => ({
...obj,
rows: obj.rows.filter((row) => row.risk === 'P1',
p2PlusRow: { count: 2, radarLink: 'link' }
}));
As you can see p2PlusRow is static values as i have not figured out how to loop through rows and get count and radarLink values. can someone let me know how to proceed in this case.
CodePudding user response:
Here is a solution that will mutate the input array. If you are ok with that, you can use this
for (const title of input) {
let count = 0;
let rdars = [];
title.rows = title.rows.filter((row) => {
if (row.risk === 'P1') {
return true;
} else {
count ;
rdars.push(row.radarLink.replace('rdar://', ''));
return false;
}
});
title['p2PlusRow'] = {};
title.p2PlusRow['count'] = count;
title.p2PlusRow['radarLink'] = 'rdar://' rdars.join('&');
}
Working snippet in case you want to see it run:
let input = [
{
title: 'ENGINEERING',
rows: [
{ risk: 'P1', radarLink: 'rdar://92642113' },
{ risk: 'P2', radarLink: 'rdar://92694095' },
{ risk: 'P3', radarLink: 'rdar://92694235' },
],
},
{
title: 'ENVIRONMENT',
rows: [
{ risk: 'P1', radarLink: 'rdar://92684289' },
{ risk: 'P2', radarLink: 'rdar://92695531' },
{ risk: 'P5', radarLink: 'rdar://92424550' },
],
},
];
for (const title of input) {
let count = 0;
let rdars = [];
title.rows = title.rows.filter((row) => {
if (row.risk === 'P1') {
return true;
} else {
count ;
rdars.push(row.radarLink.replace('rdar://', ''));
return false;
}
});
title['p2PlusRow'] = {};
title.p2PlusRow['count'] = count;
title.p2PlusRow['radarLink'] = 'rdar://' rdars.join('&');
}
console.log(input);
CodePudding user response:
Solution
You can use map()
and reduce()
to do what you want.
let input = [{
"title": "ENGINEERING",
"rows": [{
"risk": "P1",
"radarLink": "rdar://92642113"
},
{
"risk": "P2",
"radarLink": "rdar://92694095"
},
{
"risk": "P3",
"radarLink": "rdar://92694235"
},
],
},
{
"title": "ENVIRONMENT",
"rows": [{
"risk": "P1",
"radarLink": "rdar://92684289"
},
{
"risk": "P2",
"radarLink": "rdar://92695531"
},
{
"risk": "P5",
"radarLink": "rdar://92424550"
},
],
},
]
const output = input.map(department => {
const rest = department.rows.reduce((acc, cur) => {
if (cur.risk === "P1") acc.rows.push(cur);
else {
const id = cur.radarLink.split("//")[1];
acc.p2PlusRow.count ;
acc.p2PlusRow.radarLink.push(id);
}
return acc;
}, {
rows: [],
p2PlusRow: {
count: 0,
radarLink: []
}
});
rest.p2PlusRow.radarLink = `rdar://${rest.p2PlusRow.radarLink.join("&")}`
return {
title: department.title,
...rest
};
});
console.log(output);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Explanation
This looks much more complicated than it actually is.
Primarily you want to map()
as you want a result for each and every item in your array.
Then you need to reduce()
the rows
. What we do here is essentially creating a new object in the form you would like to have it. To do so we declare the following as a start value for our new object:
{
rows: [],
p2PlusRow: {
count: 0,
radarLink: [],
}
}
Now using that start value, essentially you loop over all the rows and for each row you check if risk === "P1"
. If it is you just add it to the rows
field. If it is not, we increase the counter
and push the id
within the radarLink
onto an the radarLink
array.
The result of the reduce()
therefore has the filtered rows
, the count
value and an array of ids
for the radar link. Now we only have to concatenate the resulting link using those ids
and we can return a new object which holds the result of the reduce operation and the title
as this remains untouched.
Remarks
Just two remarks on my solution:
- You would not need to actually
count
as the arrayradarLink
does the counting for you. So instead of counting withinreduce()
we could just setcount
toradarLink.length
after the reduction which is arguably a minor improvement on the above. Because the difference is certainly (almost) irrelevant and I think it is easier to understand I won't update my solution. - To make this solution completely immutable you just need to create a copy of the
risk
obect when youpush()
it onto therows
array like thisacc.rows.push({ ...cur })
. Everything else already works on copies, therefore your input remains untouched.
CodePudding user response:
You could create a helper function, called partition
, that behaves in a similar way to .filter()
, however, it allows you to keep the elements you return false
for. In the below implementation of that function, the elements you return false
for are in the first index (0
) of the returned array, and the elements that you return true
for are in the second index (index 1
) of the returned array partion()
gives you. You can then use the elements array are not P1 to get the count by taking it's .length
. You can also use this array to get the radarLink
values by mapping each non P1 row to its radarLink
without the leading rdar://
using .replace()
, and then using .join('&')
to concatenate all of them together:
const input = [ { "title": "ENGINEERING", "rows": [ { "risk": "P1", "radarLink": "rdar://92642113" }, { "risk": "P2", "radarLink": "rdar://92694095" }, { "risk": "P3", "radarLink": "rdar://92694235" }, ], }, { "title": "ENVIRONMENT", "rows": [ { "risk": "P1", "radarLink": "rdar://92684289" }, { "risk": "P2", "radarLink": "rdar://92695531" }, { "risk": "P5", "radarLink": "rdar://92424550" }, ], }, ];
const patition = (arr, pred) => {
const res = [[], []];
for(const elem of arr) {
const idx = pred(elem); // falsy = 0, truthy = 1
res[idx].push(elem);
}
return res;
}
let output = input.map((obj) => {
const [nonP1Rows, p1Rows] = patition(obj.rows, row => row.risk === "P1");
const concatRadarLinks = nonP1Rows.map(row => row.radarLink.replace('rdar://', '')).join('&');
return {
title: obj.title,
rows: p1Rows,
p2PlusRow: {count: nonP1Rows.length, radarLink: `rdar://${concatRadarLinks}`}
}
});
console.log(output);