How can I fully merge two tree objects with spread operator? - javascript

I am trying to merge two tree objects into one with the spread operator, but I am not getting the correct merged result.
How can I fully merge two tree objects with spread operator ?
const tree1 = [{
comments: [{
text: "This a comment for case law 84",
id: "84"
}, {
text: "This a comment for case law 89",
id: "89"
}],
children: [{
comments: [{
text: "This a comment for case law 70",
id: "70"
}],
children: [{
comments: [{
text: "This a comment for case law 83",
id: "83"
}]
}]
}]
}];
const tree2 = [{
comments: [{
text: "This a comment for case law 184",
id: "184"
}],
children: [{
comments: [{
text: "This a comment for case law 170",
id: "170"
}],
children: [{
comments: [{
text: "This a comment for case law 183",
id: "183"
}]
}]
}]
}];
const mergedTrees = [{ ...tree2, ...tree1 }];
console.log("mergedTrees", mergedTrees);
The problem is while merging the properties with the same key get overwritten. The rightmost property has the highest precedence.
What I need to get this kind of merge? :
{
"0": {
"comments": [
{
"text": "This a comment for case law 84",
"id": "84"
},
{
"text": "This a comment for case law 89",
"id": "89"
},
{
"text": "This a comment for case law 184",
"id": "184"
}
],
"children": [
{
"comments": [
{
"text": "This a comment for case law 70",
"id": "70"
},
{
"text": "This a comment for case law 170",
"id": "170"
}
],
"children": [
{
"comments": [
{
"text": "This a comment for case law 83",
"id": "83"
},
{
"text": "This a comment for case law 183",
"id": "183"
}
]
}
]
}
]
}
}
if it's not possible with the spread operator and there is another way to make it, please let me know.
adding a link for tries: https://stackblitz.com/edit/fffika?file=index.ts

You could merge the arrays index-wise and take the same approach for nested children.
const
merge = (a, b) => [a, b].reduce((r, array) => {
array.forEach(({ children, ...o }, i) => {
r[i] ??= { };
Object.entries(o).forEach(([k, v]) => (r[i][k] ??= []).push(...v));
if (children) r[i].children = merge(r[i].children || [], children);
});
return r;
}, []),
tree1 = [{ comments: [{ text: "This a comment for case law 84", id: "84" }], news: [{ text: "This news 1 ", id: "1" }], children: [{ comments: [{ text: "This a comment for case law 70", id: "70" }], news: [{ text: "This news 2 ", id: "2" }, { text: "This news 3 ", id: "3" }], children: [{ comments: [{ text: "This a comment for case law 83", id: "83" }], news: [{ text: "This news 4 ", id: "4" }] }] }] }],
tree2 = [{ comments: [{ text: "This a comment for case law 184", id: "184" }], news: [{ text: "This news 12 ", id: "12" }, { text: "This news 13 ", id: "13" }], children: [{ comments: [{ text: "This a comment for case law 170", id: "170" }], news: [{ text: "This news 22 ", id: "22" }, { text: "This news 33", id: "33" }], children: [{ comments: [{ text: "This a comment for case law 183", id: "183" }], news: [{ text: "This news 122 ", id: "122" }, { text: "This news 133 ", id: "133" }] }] }] }]
result = merge(tree1, tree2);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Related

Groupby multiple keys with lodash or plain js

I've been trying to group by multiple keys with lodash but it's not returning the correct results. The aim is to group by state and details but in this case it's joining/grouping docIds 333333 and 444444 which although have equal state (rejected), don't have the same id's in details (=> different people) so they don't share both conditions: state and id's in details.
It seems my current code does work with multiple criteria but once only one of the conditions exists it still performs the grouping, while I wanted to only group if both criteria were satisfied.
As for lodash approach It should show:
[
{
"docId": "222222,1111111",
"details": [
{
"id": 20656,
"type": "Claimant",
"name": "First Name Last Name"
},
{
"id": 10000,
"type": "Fellow",
"name": "Fellow First Name Fellow Last Name"
}
],
"state": "accepted",
},
{
"docId": "333333",
"details": [
{
"id": 10000,
"type": "Fellow",
"name": "Fellow First Name Fellow Last Name"
}
],
"state": "rejected",
},
{
"docId": "444444",
"details": [
{
"id": 20656,
"type": "Claimant",
"name": "First Name Last Name"
},
],
"state": "rejected",
}
]
I'm open to use plain js and have no problem in getting for example "docId": [{"333333"},{"444444"}] instead of comma separated values , or slightly different final result as long as the grouping in the end obeys to the same rules but I wasn't able to achieve the intended result either with plain js so I moved to lodash which seemed simpler.
In the end I will be needing some sorting to prioritize state, then groups with only one person in details and when one person, prioritize the ones with claimant but that's something that should be done afterwards, right?
Help would be much appreciated.
const data = [
{
docId: 222222,
state: "accepted",
details: [
{
id: 20656,
type: "Claimant",
name: "First Name Last Name",
},
{
id: 10000,
type: "Fellow",
name: "Fellow First Name Fellow Last Name",
}
]
},
{
docId: 1111111,
state: "accepted",
details: [
{
id: 10000,
type: "Fellow",
name: "Fellow First Name Fellow Last Name",
},
{
id: 20656,
type: "Claimant",
name: "First Name Last Name",
}
]
},
{
docId: 333333,
state: "rejected",
details: [
{
id: 10000,
type: "Fellow",
name: "Fellow First Name Last Name",
}
]
},
{
docId: 444444,
state: "rejected",
details: [
{
id: 20656,
type: "Claimant",
name: "First Name Last Name",
}
]
}
];
const grouped = _(data)
.groupBy(({details,state}) => `${details},${state}`)
.map((value, key) => ({
docId: _.map(value, 'docId').join(','),
details: value[0].details,
state: value[0].state,
document_file_name: value[0].document_file_name,
}))
.value()
console.log(grouped)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
First collect id by state and docId.
Second group collected data by state and sorted id and build new objects.
const
data = [{ docId: 222222, document_file_name: "4020653_FileName.pdf", document_updated_at: "2020-07-08T19:41:28.385Z", state: "accepted", details: [{ id: 20656, type: "Claimant", name: "First Name Last Name" }, { id: 10000, type: "Fellow", name: "Fellow First Name Fellow Last Name" }] }, { docId: 1111111, document_file_name: "4020600_FileName.pdf", document_updated_at: "2020-07-08T19:41:28.385Z", state: "accepted", details: [{ id: 10000, type: "Fellow", name: "Fellow First Name Fellow Last Name" }, { id: 20656, type: "Claimant", name: "First Name Last Name" }] }, { docId: 333333, document_file_name: "4020890_FileName.pdf", document_updated_at: "2020-07-08T19:41:28.385Z", state: "rejected", details: [{ id: 10000, type: "Fellow", name: "Fellow First Name Last Name" }] }, { docId: 444444, document_file_name: "4020672_FileName.pdf", document_updated_at: "2020-07-08T19:41:28.385Z", state: "rejected", details: [{ id: 20656, type: "Claimant", name: "First Name Last Name" }] }],
ids = {},
temp = data.reduce((r, { docId, state, details }) => {
const key = [state, docId].join('|');
details.forEach(o => {
ids[o.id] = o;
(r[key] ??= []).push(o.id);
});
return r;
}, {}),
grouped = Object.values(Object
.entries(temp)
.reduce((r, [k, v]) => {
const
[state, docId] = k.split('|'),
key = [state, v.sort().join('|')].join('#');
r[key] ??= { docId: '' , details: v.map(id => ids[id]), state };
r[key].docId += (r[key].docId && ', ') + docId;
return r;
}, {}));
console.log(grouped);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Combine and flatten elements of array

With the below JSON content that is actually coming from an API but I'm using a json file for testing. I would like to combine the primary key and flatten the ItemList.
[{
"PrimaryKey": "123",
"ItemList": [
{
"SecondaryKey": "ABC",
"Name": "Item1",
"Description": "Sample item"
},
{
"SecondaryKey": "DEF",
"Name": "Item2",
"Description": "Another sample item"
}
],
"IgnoreThis": [
{
"SomeData": "Some Data"
}
]
}]
The output I would like is:
[{
"PrimaryKey": 123,
"SecondaryKey": "ABC",
"Name": "Item1",
"Description": "Sample Item"
},
{
"PrimaryKey": 123,
"SecondaryKey": "DEF",
"Name": "Item2",
"Description": "Another sample item"
}]
I've got the Item list being flattened by:
let items = [];
items.push(JSON.parse(fs.readFileSync('./items.json')));
let result = items.reduce((r, obj) => r.concat(obj.ItemList), []);
I've tried to use items.map to get the desired output nothing has worked, I don't think I understand how to chain .map and .reduce effectively as I get undefined as the result.
Any ideas how I can achieve this output?
You can do this by running map twice: get the PrimaryKey from the first map, then add it to all the objects inside the second map, then you flatten the array you got from the previous stage.
const data = [
{
PrimaryKey: "123",
ItemList: [
{
SecondaryKey: "ABC",
Name: "Item1",
Description: "Sample item",
},
{
SecondaryKey: "DEF",
Name: "Item2",
Description: "Another sample item",
},
],
IgnoreThis: [
{
SomeData: "Some Data",
},
],
},
{
PrimaryKey: "456",
ItemList: [
{
SecondaryKey: "ABC",
Name: "Item1",
Description: "Sample item",
},
{
SecondaryKey: "DEF",
Name: "Item2",
Description: "Another sample item",
},
],
IgnoreThis: [
{
SomeData: "Some Data",
},
],
},
];
const result = data.map(({ PrimaryKey, ItemList }) => ItemList.map(item => ({
PrimaryKey,
...item,
}))).flat();
console.log(result);

Ancestors path to be reformat for hierarchy nodes object

I had reformat my data in mongodb using ancestors path to ensure the relationship of each nodes & intend to show it in a organisation tree view.
[
{
"isActive": true,
"ancestorsPath": [],
"studentGroup": [
"t2-001",
"t2-002",
"t2-003"
],
"_id": "33d9704f34901855c911e433",
"email": "user5#test.com",
"agentId": "t1-001"
},
{
"isActive": true,
"ancestorsPath": [],
"studentGroup": [
"t2-101",
"t2-102",
"t2-103"
],
"_id": "5cdb0664c409e90e4fe21133",
"email": "user5#test.com",
"agentId": "t1-100",
},
{
"isActive": true,
"ancestorsPath": [
"t1-001"
],
"studentGroup": [
"t3-001",
"t3-002",
"t3-003"
],
"_id": "5cdb06dec409e90e4fe21134",
"email": "user5#test.com",
"agentId": "t2-001",
}
]
However the data in organisation tree is using a format as below:
id: "T1-001",
name: "user5#test.com",
title: "general manager",
children: [
{
id: "t2-001",
name: "Bo Miao",
title: "department manager",
number: "MYR 2,000"
},
{
id: "t2-002",
name: "Su Miao",
title: "department manager",
children: [
{ id: "t3-001", name: "Tie Hua", title: "senior engineer" },
{
id: "t3-002",
name: "Hei Hei",
title: "senior engineer",
children: [
{ id: "6", name: "Pang Pang", title: "engineer" },
{ id: "7", name: "Xiang Xiang", title: "UE engineer" }
]
}
]
},
{ id: "t2-003", name: "Hong Miao", title: "department manager" }
How should I format my data? Can anyone give me some pointers so i can study further?

How to convert array of objects to single object which has dynamic key in typescript

This question might be similar to frequently asked one, but this one has some different approach.
In my angular 7 application, I have the following 5 arrays which needs to be converted to the below single object with dynamic key based on the id.
{
"enabled-41": true,
"enabled-42": true,
"enabled-43": true,
"enabled-44": true,
"enabled-45": false,
"abc-41": "some description 1",
"abc-42": "some description 12",
"abc-43": "some description 123",
"abc-44": "some description 1234",
"abc-45": null,
"def-41": "some description 2",
"def-42": "some description 23",
"def-43": "some description 234",
"def-44": "some description 2345",
"def-45": null,
"type-41": "def",
"type-42": "abc",
"type-43": "def",
"type-44": "abc",
"type-45": null,
"weight-41": "25",
"weight-42": "25",
"weight-43": "25",
"weight-44": "25",
"weight-45": null
}
let arr = [
{
"id": 41,
"abc": "some description 1",
"def": "some description 2",
"type": "def",
"Criteria": {
"id": 5,
"question": "follow-up",
"definition": "definition content",
"status": true
},
"weight": 25,
"enabled": true
},
{
"id": 42,
"abc": "some description 12",
"def": "some description 23",
"type": "abc",
"Criteria": {
"id": 1,
"question": "coverage",
"definition": "definition content",
"status": true
},
"weight": 25,
"enabled": true
},
{
"id": 43,
"abc": "some description 123",
"def": "some description 234",
"type": "def",
"Criteria": {
"id": 4,
"question": "Price",
"definition": "definition content",
"status": true
},
"weight": 25,
"enabled": true
},
{
"id": 44,
"abc": "some description 1234",
"def": "some description 2345",
"type": "abc",
"Criteria": {
"id": 3,
"question": "Exchange",
"definition": "definition content",
"status": true
},
"weight": 25,
"enabled": true
},
{
"id": 45,
"Criteria": {
"id": 2,
"definition": "definition conent",
"question": "Random",
"status": true
},
"type": null,
"abc": null,
"def": null,
"weight": 0,
"enabled": false
}
];
let result = arr.reduce(function(obj, item) {
obj[item] = item.value;
return obj;
}, {})
console.log(result);
I have tried using reduce function, but cannot able to get the right way to convert to a single object with the above format based on dynamic key (joining id with hypen).
Can someone help me with this?
You can use reduce with Object.keys, and place all keys you wish to exclude in an array and check against that:
let arr = [{"id":41,"abc":"some description 1","def":"some description 2","type":"def","Criteria":{"id":5,"question":"follow-up","definition":"definition content","status":true},"weight":25,"enabled":true},{"id":42,"abc":"some description 12","def":"some description 23","type":"abc","Criteria":{"id":1,"question":"coverage","definition":"definition content","status":true},"weight":25,"enabled":true},{"id":43,"abc":"some description 123","def":"some description 234","type":"def","Criteria":{"id":4,"question":"Price","definition":"definition content","status":true},"weight":25,"enabled":true},{"id":44,"abc":"some description 1234","def":"some description 2345","type":"abc","Criteria":{"id":3,"question":"Exchange","definition":"definition content","status":true},"weight":25,"enabled":true},{"id":45,"Criteria":{"id":2,"definition":"definition conent","question":"Random","status":true},"type":null,"abc":null,"def":null,"weight":0,"enabled":false}];
let exclude = ["id", "Criteria"];
let result = arr.reduce((acc, curr) => {
let id = curr.id;
Object.entries(curr).forEach(([k, v]) => {
if (!exclude.includes(k)) acc[`${k}-${id}`] = v;
});
return acc;
}, {});
console.log(result);
You code is almost there. But object keys order is not guaranteed. Inside the reduce callback function add the keys in the accumulator and corresponding value.
Use template literals & square notation while creating the object keys
let arr = [{
"id": 41,
"abc": "some description 1",
"def": "some description 2",
"type": "def",
"Criteria": {
"id": 5,
"question": "follow-up",
"definition": "definition content",
"status": true
},
"weight": 25,
"enabled": true
},
{
"id": 42,
"abc": "some description 12",
"def": "some description 23",
"type": "abc",
"Criteria": {
"id": 1,
"question": "coverage",
"definition": "definition content",
"status": true
},
"weight": 25,
"enabled": true
},
{
"id": 43,
"abc": "some description 123",
"def": "some description 234",
"type": "def",
"Criteria": {
"id": 4,
"question": "Price",
"definition": "definition content",
"status": true
},
"weight": 25,
"enabled": true
},
{
"id": 44,
"abc": "some description 1234",
"def": "some description 2345",
"type": "abc",
"Criteria": {
"id": 3,
"question": "Exchange",
"definition": "definition content",
"status": true
},
"weight": 25,
"enabled": true
},
{
"id": 45,
"Criteria": {
"id": 2,
"definition": "definition conent",
"question": "Random",
"status": true
},
"type": null,
"abc": null,
"def": null,
"weight": 0,
"enabled": false
}
];
let result = arr.reduce(function(obj, item) {
obj[`enabled-${item.id}`] = item.enabled;
obj[`abc-${item.id}`] = item.abc;
obj[`def-${item.id}`] = item.def;
obj[`type-${item.id}`] = item.type;
obj[`weight-${item.id}`] = item.weight;
return obj;
}, {});
console.log(result)
Assuming you want to exclude all the properties whose value is an object maybe you can go with this generic idea that uses Object.entries() to traverse the inner objects and some destructuring features.
let arr=[{"id":41,"abc":"some description 1","def":"some description 2","type":"def","Criteria":{"id":5,"question":"follow-up","definition":"definition content","status":true},"weight":25,"enabled":true},{"id":42,"abc":"some description 12","def":"some description 23","type":"abc","Criteria":{"id":1,"question":"coverage","definition":"definition content","status":true},"weight":25,"enabled":true},{"id":43,"abc":"some description 123","def":"some description 234","type":"def","Criteria":{"id":4,"question":"Price","definition":"definition content","status":true},"weight":25,"enabled":true},{"id":44,"abc":"some description 1234","def":"some description 2345","type":"abc","Criteria":{"id":3,"question":"Exchange","definition":"definition content","status":true},"weight":25,"enabled":true},{"id":45,"Criteria":{"id":2,"definition":"definition conent","question":"Random","status":true},"type":null,"abc":null,"def":null,"weight":0,"enabled":false}];
let result = arr.reduce((obj, {id, ...rest}) =>
{
Object.entries(rest).forEach(([k, v]) =>
{
if (Object(v) !== v) obj[`${k}-${id}`] = v;
});
return obj;
}, {});
console.log(result);
.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}
Oh, man... I just got beat. Here's my solution.
let arr= [] // hold the final object array
let keys = [] // temp item to hold the value of each key
// iterate over each key
Object.keys(input).forEach((key) => {
let pair = key.split('-') // split the key into the real key and the index
// if the index isn't in the array, push it there (this keeps the same order)
if (keys.indexOf(pair[1])===-1) {
keys.push(pair[1])
}
// use object.assign to add the keys to the existing object in the right place in the array.
arr[keys.indexOf(pair[1])] = Object.assign({}, arr[keys.indexOf(pair[1])], {[pair[0]]: input[key]}, { id: pair[1] })
})
function getFilteredData(arr) {
const result = {};
arr.forEach(item => {
const { id, Criteria, ...rest } = item;
Object.entries(rest).map(([key, value]) => {
result[`${key}-${id}`] = value;
});
});
return result;
}
let arr = [
{
id: 41,
abc: 'some description 1',
def: 'some description 2',
type: 'def',
Criteria: {
id: 5,
question: 'follow-up',
definition: 'definition content',
status: true
},
weight: 25,
enabled: true
},
{
id: 42,
abc: 'some description 12',
def: 'some description 23',
type: 'abc',
Criteria: {
id: 1,
question: 'coverage',
definition: 'definition content',
status: true
},
weight: 25,
enabled: true
},
{
id: 43,
abc: 'some description 123',
def: 'some description 234',
type: 'def',
Criteria: {
id: 4,
question: 'Price',
definition: 'definition content',
status: true
},
weight: 25,
enabled: true
},
{
id: 44,
abc: 'some description 1234',
def: 'some description 2345',
type: 'abc',
Criteria: {
id: 3,
question: 'Exchange',
definition: 'definition content',
status: true
},
weight: 25,
enabled: true
},
{
id: 45,
Criteria: {
id: 2,
definition: 'definition conent',
question: 'Random',
status: true
},
type: null,
abc: null,
def: null,
weight: 0,
enabled: false
}
];
console.log(getFilteredData(arr));

javascript iterate over objects in nested array of objects and filter by ids and return the same original dataset updated

I have an array of nested objects, that i want to keep the objects in items that have the id equal to one in the filter list that i have as reference.
const data = [{
"otherStuff": "otherB",
"items": {
"item1": [{
"id": "id1",
"info": "info1"
},
{
"id": "id2",
"info": "info22"
}
],
"item20": [{
"id": "id3",
"info": "info5"
}],
"item5": [{
"id": "id4",
"info": "info6"
},
{
"id": "id5",
"info": "info7"
}
]
}
}, {
"otherStuff": "otherA",
"items": {
"item1": [{
"id": "id1",
"info": "info10000"
},
{
"id": "id2",
"info": "info220000"
}
],
"item20": [{
"id": "id3",
"info": "info5000"
}],
"item5": [{
"id": "id4",
"info": "info60000"
},
{
"id": "id5",
"info": "info7000"
}
]
}
}];
const keep = ['id4', 'id2'];
keep.forEach(function(val) {
data.forEach(function(entry, index){
const entrySelected = [];
Object.keys(entry.items).forEach(item => {
var match = entry.items[item].find(obj => obj.id === val);
if (match) {
entrySelected.push(match)
}
});
data[index].items = entrySelected;
});
})
console.log(data)
I am getting an error right now,
but the idea is to get the following output:
[
{
"otherStuff": "otherB",
"items": [
{
"id": "id2",
"info": "info22"
},
{
"id": "id4",
"info": "info6"
}
]
},
{
"otherStuff": "otherA",
"items": [
{
"id": "id2",
"info": "info220000"
},
{
"id": "id4",
"info": "info60000"
}
]
}
]
how can i achieve this?
You could iterate the values of the objects and then reduce the wanted items for a new array. Later assing this to items.
var data = [{ otherStuff: "otherB", items: { item1: [{ id: "id1", info: "info1" }, { id: "id2", info: "info22" }], item20: [{ id: "id3", info: "info5" }], item5: [{ id: "id4", info: "info6" }, { id: "id5", info: "info7" }] } }, { otherStuff: "otherA", items: { item1: [{ id: "id1", info: "info10000" }, { id: "id2", info: "info220000" }], item20: [{ id: "id3", info: "info5000" }], item5: [{ id: "id4", info: "info60000" }, { id: "id5", info: "info7000" }] } }],
keep = ['id4', 'id2'];
data.forEach(o =>
o.items = Object
.keys(o.items)
.reduce((r, k) => o
.items[k]
.reduce((s, p) => s.concat(keep.includes(p.id) ? p : []), r), []));
console.log(data);
.as-console-wrapper { max-height: 100% !important; top: 0; }
With a filter for arrays.
var data = [{ otherStuff: "otherB", items: { item1: [{ id: "id1", info: "info1" }, { id: "id2", info: "info22" }], item20: [{ id: "id3", info: "info5" }], item5: [{ id: "id4", info: "info6" }, { id: "id5", info: "info7" }] } }, { otherStuff: "otherA", items: { item1: [{ id: "id1", info: "info10000" }, { id: "id2", info: "info220000" }], item20: [{ id: "id3", info: "info5000" }], item5: [{ id: "id4", info: "info60000" }, { id: "id5", info: "info7000" }] } }],
keep = ['id4', 'id2'];
data.forEach(o =>
o.items = Object
.keys(o.items)
.filter(k => Array.isArray(o.items[k]))
.reduce((r, k) => o
.items[k]
.reduce((s, p) => s.concat(keep.includes(p.id) ? p : []), r), []));
console.log(data);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Categories