Flatten multi dimension array object to one dimensional array object - javascript

i am having trouble to converting multidimensional array something like this and need help to flatten this array object into single dimensional array object.
let data = [
{
parent_id: 1,
name: "parent 1",
child: [
{
child_id: 3,
name: "child 1",
child: [
{
grand_child_id: 5,
name: "grand child 1",
},
{
grand_child_id: 6,
name: "grand child 2",
},
],
},
],
},
{
parent_id: 2,
name: "parent 2",
child: [
{
child_id: 4,
name: "child ",
child: [
{
grand_child_id: 7,
name: "grand child 3",
},
{
grand_child_id: 8,
name: "grand child 4",
},
],
},
],
},
];
I have try using flatMap() but the result is not what i`m expecting, I'm hoping the result is single dimensional array like this. Its look like i have some trouble on recursively flat map the child and grand child array.
let result = [
{
parent_id: 1,
child_id: 3,
grand_child_id: 5,
},
{
parent_id: 1,
child_id: 3,
grand_child_id: 6,
},
{
parent_id: 2,
child_id: 4,
grand_child_id: 7,
},
{
parent_id: 2,
child_id: 4,
grand_child_id: 8,
},
];

Only works for 3 levels.
let result = [];
for(let firstParent of data){
let { parent_id, child = []} = firstParent;
for(let secondParent of child){
let {child_id, child:grandChilds = [] } = secondParent;
for(let grandChild of grandChilds){
let {grand_child_id} = grandChild;
result.push({
parent_id,
child_id,
grand_child_id
})
}
}
}
console.log(result);

i finally got some function to solve my problem, i`m using flatMap() lodash to achieve this. thanks :)
function getChild(n) {
let children = n.child.map((el) => {
return { child_id: el.child_id, child: el.child };
})[0];
return _.map(children.child, function (item) {
return {
parent_id: n.parent_id,
child_id: children.child_id,
grand_child_id: item.grand_child_id,
};
});
}
console.log(_.flatMap(data, getChild));

Related

Nested arrays and targeting array by keys

I'm really not sure how to word this issue but I will try my best.
I have a nested array:
const items = [
{
id: 1,
name: "Test name",
children: [
{
id: 5,
name: "Test name 5",
children: [
{
id: 6,
name: "Test name 6",
children: [],
},
],
},
],
},
{
id: 8,
name: "Test name 8",
children: [
{
id: 9,
name: "Test name 9",
children: [],
},
],
},
];
and I have an array of indexes where to target and update
const keys = [0,0,0]
The array of indexes should target Test name 6
How can I update Test name 6 to something else?
p.s. items and keys are dynamic. there might be dozens of nested items or dozens of indexes in keys
You could reduce the indices and check the children property.
After getting the final object, just assign the value to the wanted property.
const
getItem = (children, keys) => keys.reduce(
({ children = [] }, i) => children[i],
{ children }
),
items = [{ id: 1, name: "Test name", children: [{ id: 5, name: "Test name 5", children: [{ id: 6, name: "Test name 6", children: [] }] }] }, { id: 8, name: "Test name 8", children: [{ id: 9, name: "Test name 9", children: [] }] }],
keys = [0, 0, 0];
console.log(getItem(items, keys));
To update your nested array first you will first need to change it to a mutable variable, i.e. from a const to a let or var.
From there you can update the value with something like:
// Copy pasted data from your post
let items = [
{
id: 1,
name: "Test name",
children: [
{
id: 5,
name: "Test name 5",
children: [
{
id: 6,
name: "Test name 6",
children: [],
},
],
},
],
},
{
id: 8,
name: "Test name 8",
children: [
{
id: 9,
name: "Test name 9",
children: [],
},
],
},
];
const keys = [0,0,0]
// <----------------- CODE BELOW ---------------->
// var to store item each time you dive deeper into the nested array
// initalize with the first item
let item = items[keys[0]].children;
// loop through each key
for(let i = 1; i < keys.length; i++){
// if we are at the last key, set item equal to the item object
if(i == keys.length-1){
item = item[keys[i]];
}
// otherwise, set item equal to the item's children
else{
item = item[keys[i]].children
}
}
// once item has been reached - all keys have been looped through - data can be minupulated
item.name = "new value"
// "Test name 6" has been updated to "new value"
emphasized text
In this scenario, I usually use a Map referencing the items within the nested arrays. For example:
const items = [
{
id: 1,
name: "Test name",
children: [
{
id: 5,
name: "Test name 5",
children: [
{
id: 6,
name: "Test name 6",
children: [],
},
],
},
],
},
{
id: 8,
name: "Test name 8",
children: [
{
id: 9,
name: "Test name 9",
children: [],
},
],
},
];
const createItemsMap = items => {
const m = new Map();
(function _collect(items) {
for (const item of items) {
m.set(item.id, item);
if (item.children) {
_collect(item.children);
}
}
})(items);
return m;
}
const itemsMap = createItemsMap(items);
// modify item #6
itemsMap.get(6).name = "Modified name 6";
console.log("itemsMap.get(1) === items[0] is", itemsMap.get(1) === items[0]);
console.log(items);
With that map, modifying the items is simply a matter of doing this :
const keys = [1,5,6];
for (const key of keys)
const item = itemsMap.get(key);
// update item
item.name = `Updated name ${key}`;
item.newProp = 'New value!';
}
// ex:
console.log(items[0]);
// {
// id: 1,
// name: "Updated name 1",
// newProp: "New value!',
// children: [
// {
// id: 5,
// name: "Updated name 5",
// newProps: "New value!",
// children: [
// {
// id: 6,
// name: "Updated name 6",
// newProps: "New value!",
// children: [],
// },
// ],
// },
// ],
// },
And a few more freebies :
// get all existing keys
const keys = Array.from(itemsMap.keys());
// check if an item exists
const exists3 = itemsMap.has(3); // false
const exists5 = itemsMap.has(5); // true
// adding new children
const newChild = { id: 3, name: "Test name 3", children: [] };
// ... add as child of 5
itemsMap.get(5).children.push(newChild);
// ... add it to the map
itemsMap.set(newChild.id, newChild); // that's it!
// remove an item
const removed = itemsMap.get(3);
// ... find if there is a parent...
const parent = Array.from(itemsMap.values()).find(item =>
item.children.includes(removed)
);
if (parent) {
// ... if there is a parent, remove the child
parent.children = parent.children.filter(item =>
item !== removed
);
} else {
// ... otherwise it is a root item, so remove it
items = items.filter(item => item !== removed);
}
// ... remove from map
itemsMap.delete(removed.id);
Note: If items is modified directly (i.e. items are added or removed), then itemsMap needs to either be re-generated, or preferably updated. In any other case, both items and itemsMap reference to the same data.

How to flatten an object that has array of objects in Javascript

I am trying to solve this question it needs me to flatten this object parent that it has children each parent has 2 children, and each child has 2 children and so on....
My goal is to flatten this to one single object.
const par = {
id: 1,
name: "parent",
children: [{
id: 2,
name: "child 1",
children:[{
id: 4,
name: "child 3",
children: [],
},{
id: 5,
name: "child 4 ",
}]
},{
id: 3,
name: "child 2",
children: [{
id: 6,
name: "child 5",
},{
id: 7,
name: "child 6",
children: []
}]
}]
}
I tried function, but it returns an array from
Deep Flatten JavaScript Object Recursively
function flat(r, a) {
let b = {};
Object.keys(a).forEach(function (k) {
if (k !== 'children') {
b[k] = a[k];
}
});
r.push(b);
if (Array.isArray(a.children)) {
b.children = a.children.map(function (a) { return a.id;});
return a.children.reduce(flat, r);
}
return r;
}
You still owe us a description of your desired output. But if you want something as simple as this:
[
{id: 1, name: "parent"},
{id: 2, name: "child 1"},
{id: 4, name: "child 3"},
{id: 5, name: "child 4"},
{id: 3, name: "child 2"},
{id: 6, name: "child 5"},
{id: 7, name: "child 6"}
]
Then a depth-first recursive function can be as simple as this:
const flatten = ({children = [], ...rest}) => [rest, ...children .flatMap (flatten)]
const par = {id: 1, name: "parent", children: [{id: 2, name: "child 1", children: [{id: 4, name: "child 3", children: []}, {id: 5, name: "child 4 ", }]}, {id: 3, name: "child 2", children: [{id: 6, name: "child 5", }, {id: 7, name: "child 6", children: []}]}]}
console .log (flatten (par))
.as-console-wrapper {max-height: 100% !important; top: 0}
If you wanted to include a parentId field, using null for root-level objects, it's only slightly more complex:
const flatten = ({id, children = [], ...rest}, parentId = null) => [
{id, ...rest, parentId}, ...children .flatMap (c => flatten(c, id))
]
Here's an effective technique using a recursive generator flat -
function *flat({ children = [], ...t }, parentId = null) {
yield { ...t, parentId }
for (const child of children)
yield *flat(child, t.id)
}
const par = {id: 1,name: "parent",children: [{id: 2,name: "child 1",children:[{id: 4,name: "child 3",children: [],},{id: 5,name: "child 4 ",}]},{id: 3,name: "child 2",children: [{id: 6,name: "child 5",},{id: 7,name: "child 6",children: []}]}]}
console.log(Array.from(flat(par)))
.as-console-wrapper { min-height: 100%; top: 0; }
You can collect all the results of a generator using Array.from -
[
{
"id": 1,
"name": "parent",
"parentId": null
},
{
"id": 2,
"name": "child 1",
"parentId": 1
},
{
"id": 4,
"name": "child 3",
"parentId": 2
},
{
"id": 5,
"name": "child 4 ",
"parentId": 2
},
{
"id": 3,
"name": "child 2",
"parentId": 1
},
{
"id": 6,
"name": "child 5",
"parentId": 3
},
{
"id": 7,
"name": "child 6",
"parentId": 3
}
]
Or you can simply iterate thru the generator's result directly -
for (const flatNode of flat(par)) {
// do something with flatNode ...
}
See this related Q&A for a technique to convert the flat tree back to a recursive tree or graph.
You can try this
function flatTree(tree, parentId = null) {
const { id, name, children } = tree;
const result = [{ id, name, parentId }];
if (Array.isArray(children)) {
children.forEach((child) => {
result.push(...flatTree(child, id));
});
}
return result;
}
const par = {
id: 1,
name: "parent",
children: [
{
id: 2,
name: "child 1",
children: [
{
id: 4,
name: "child 3",
children: [],
},
{
id: 5,
name: "child 4 ",
},
],
},
{
id: 3,
name: "child 2",
children: [
{
id: 6,
name: "child 5",
},
{
id: 7,
name: "child 6",
children: [],
},
],
},
],
};
console.log(flatTree(par));
/**
* Output:
* [
{ id: 1, name: 'parent', parentId: null },
{ id: 2, name: 'child 1', parentId: 1 },
{ id: 4, name: 'child 3', parentId: 2 },
{ id: 5, name: 'child 4 ', parentId: 2 },
{ id: 3, name: 'child 2', parentId: 1 },
{ id: 6, name: 'child 5', parentId: 3 },
{ id: 7, name: 'child 6', parentId: 3 }
]
*/
Here is a solution using object-scan. Reinventing the wheel is typically not as bug-free, flexible or maintainable as using a battle-tested library!
.as-console-wrapper {max-height: 100% !important; top: 0}
<script type="module">
import objectScan from 'https://cdn.jsdelivr.net/npm/object-scan#18.4.0/lib/index.min.js';
const par = { id: 1, name: 'parent', children: [{ id: 2, name: 'child 1', children: [{ id: 4, name: 'child 3', children: [] }, { id: 5, name: 'child 4 ' }] }, { id: 3, name: 'child 2', children: [{ id: 6, name: 'child 5' }, { id: 7, name: 'child 6', children: [] }] }] };
const fn = objectScan(['**{children[*]}.id'], {
rtn: ({ parent: { id, name } }) => ({ id, name })
});
const r = fn(par);
console.log(r);
/* => [
{ id: 7, name: 'child 6' },
{ id: 6, name: 'child 5' },
{ id: 3, name: 'child 2' },
{ id: 5, name: 'child 4 ' },
{ id: 4, name: 'child 3' },
{ id: 2, name: 'child 1' },
{ id: 1, name: 'parent' }
] */
</script>
Disclaimer: I'm the author of object-scan

Filter javascript array with subarray childern and return parent

Thanks in advance.
Iam having the below array and am trying to work out with array filter in javascript.
[{ID: 16,ItemName: "SomeData 1", children : []},
{ID: 19,ItemName: "SomeData 2", children : [{ID: 51,ItemName: "SomeData 3"},{ID: 41,ItemName: "SomeData 4"},{ID: 16,ItemName: "SomeData 5"}]}
{ID: 14, ItemName: "SomeData 4", children : [{ID: 21,ItemName: "SomeData 9"}]}]
If I search for SomeData 4 then using filter it should return me something like
[{ID: 19,ItemName: "SomeData 2", children : [{ID: 41,ItemName: "**SomeData 4**"}]}
{ID: 14, ItemName: "**SomeData 4**", children : [{ID: 21,ItemName: "SomeData 9"}]}]
Q1)
That the if the search keywords matches with any object in child then it should return include its parent content.
Even if it matches with parent then it should return the parent and child.(I have done with this part)
const filteredContent = ItemsList.filter(item => {
return item.ItemName.indexOf(value) !== -1;
});
Can anyone can suggest a best way to excute Q1
You could take a recursive approach and check the actual object contains the wanted value or if children contains a wanted value.
If children contains a result, build a new object with updated children property.
const
find = (r, o) => {
if (o.ItemName === search) r.push(o);
else {
var children = (o.children || []).reduce(find, []);
if (children.length) r.push(Object.assign({}, o, { children }));
}
return r;
};
var data = [{ ID: 16, ItemName: "SomeData 1", children: [] }, { ID: 19, ItemName: "SomeData 2", children: [{ ID: 51, ItemName: "SomeData 3" }, { ID: 41, ItemName: "SomeData 4" }, { ID: 16, ItemName: "SomeData 5" }] }, { ID: 14, ItemName: "SomeData 4", children: [{ ID: 21, ItemName: "SomeData 9" }] }],
search = 'SomeData 4',
result = data.reduce(find, []);
console.log(result);
Modified Solution for nchildren in a object and their children to n iterations.
Use the same function but recursively. The below function works.
var data = [{
ID: 16,ItemName: "SomeData 1",
children: [],children: [{ID: 73,ItemName: "SomeData 3"},
{ID: 57,ItemName: "SomeData 4"},
{ID: 59,ItemName: "SomeData 5"}]
},
{
ID: 19,ItemName: "SomeData 2",
children: [{ID: 51,ItemName: "SomeData 3"},
{ID: 41,ItemName: "SomeData 4"},
{ID: 16,ItemName: "SomeData 5"}]
},
{
ID: 14,ItemName: "SomeData 4",
children: [{ID: 21,ItemName: "SomeData 9"}]
}
]
const filteredContent = (ItemsList, value) => {
return ItemsList.filter(item => {
let hasValue = item.ItemName.indexOf(value) !== -1
if (!hasValue) {
for (let key in item) {
if (key === 'children') {
item.children = filteredContent(item.children, value);
hasValue = item.children && item.children.length > 0;
}
}
}
return hasValue
});
}
console.log(filteredContent(data, 'SomeData 4'));
Hope this is the solution you are expecting.
You can traverse your array recursionally:
const search = (arr, keyWord) => {
arr.forEach(element => {
if ((element.ItemName == keyWord && element.children)) {
searchResult.push(element);
}
if ((element.children && element.children.some(s=> s.ItemName == keyWord))) {
let newObj = {...element};
newObj.children = newObj.children.filter(f => f.ItemName == keyWord);
searchResult.push(newObj);
}
if ( Array.isArray(element.children) && element.children.length > 0) {
search(element.children, keyWord);
}
});
}
An example:
let arr = [
{ ID: 16, ItemName: "SomeData 1", children: [] },
{ ID: 19, ItemName: "SomeData 2", children: [{ ID: 51, ItemName: "SomeData 3" }, { ID: 41, ItemName: "SomeData 4" }, { ID: 16, ItemName: "SomeData 5" }] },
{ ID: 14, ItemName: "SomeData 4", children: [{ ID: 21, ItemName: "SomeData 9" }] }];
let searchResult = [];
let wordToFind = 'SomeData 4';
const search = (arr, keyWord) => {
arr.forEach(element => {
if ((element.ItemName == keyWord && element.children)) {
searchResult.push(element);
}
if ((element.children && element.children.some(s=> s.ItemName == keyWord))) {
let newObj = {...element};
newObj.children = newObj.children.filter(f => f.ItemName == keyWord);
searchResult.push(newObj);
}
if ( Array.isArray(element.children) && element.children.length > 0) {
search(element.children, keyWord);
}
});
}
search(arr, 'SomeData 4');
console.log(searchResult);

Which is the best way to get an array difference from an object?

I have an array of values: ["1", "2", "3"] which contains essentially the reference of the records stored in this array of object:
[
{ id: 1, name: "John" },
{ id: 2, name: "Patrick" },
{ id: 3, name: "Jack" },
{ id: 4, name: "Paula" },
{ id: 5, name: "Sarah" }
]
I would like to return the missing reference from the array of objects, so the result will be: 4, 5. What I achieved so far is takes all the selected values of the first array from all the select available in the html:
var selected_options = $('.options-picker')
.map(function() { return this.value}).get();
this will return 1, 2, 3. How can I extract from the array of objects 4, 5?
Thanks in advance.
Use filter and includes to check the object ids against the values in the array.
const data = [
{ id: 1, name: "John" },
{ id: 2, name: "Patrick" },
{ id: 3, name: "Jack" },
{ id: 4, name: "Paula" },
{ id: 5, name: "Sarah" }
];
const items = [1, 2, 3];
const out = data.filter(obj => !items.includes(obj.id));
console.log(out);
This will do
var a=[
{ id: 1, name: "John" },
{ id: 2, name: "Patrick" },
{ id: 3, name: "Jack" },
{ id: 4, name: "Paula" },
{ id: 5, name: "Sarah" }
]
var b=['1', '2', '3'];
a.forEach((e)=>{
if(b.indexOf(e.id.toString())==-1)
{
b.push(e.id);
}
})
alert(b)

How best to restructure JSON child data object arrays into parent array

We have some JSON data where each parent array object can have a property that is an array of "children" along the lines of the following :
data: [
{
value: 1,
parent_id: null,
label: "1.0 - TT One",
children: [
{
value: 3,
label: "1.1 - TT One-One",
parent_id: 1,
},
{
value: 4,
label: "1.2 - TT One-Two",
parent_id: 1,
}
]
},
{
value: 2,
parent_id: null,
label: "2.0 - TT Two",
children: [
{
value: 5,
label: "2.1 - TT Two-One",
parent_id: 2,
}
]
}
]
We'd like to "flatten" the children so that we end up with one array that is all the parents and children as follows (it does not have to stay named data if not efficient):
data: [
{
value: 1,
parent_id: null,
label: "1.0 - TT One"
},
{ <-- FORMER CHILD
value: 3,
label: "1.1 - TT One-One",
parent_id: 1
},
{ <-- FORMER CHILD
value: 4,
label: "1.2 - TT One-Two",
parent_id: 1,
},
{
value: 2,
parent_id: null,
label: "2.0 - TT Two"
},
{ <-- FORMER CHILD
value: 5,
label: "2.1 - TT Two-One",
parent_id: 2,
}
]
Thoughts on how best to accomplish this in an efficient manor? We have underscore if that will help.
Found a buttload of array to array flatten and some object to array flattens, but nothing combining the two. If we had this, we wouldn't have needed to post. If it's not obvious from exactly what we have to exactly what we need, what is the point?
the point is to understand what is your data structure and how to visit every element of that data structure.
Since there are only 2 levels you do not even need to find a general solution to transform your initial data into an array. Do you know how to traverse an array? then you know how to traverse an element that has an array as property as well.
Anyway here is both a recursive and a non recursive generalized solution.
var d = [{
value: 1,
parent_id: null,
label: "1.0 - TT One",
children: [{
value: 3,
label: "1.1 - TT One-One",
parent_id: 1,
},
{
value: 4,
label: "1.2 - TT One-Two",
parent_id: 1,
}
]
},
{
value: 2,
parent_id: null,
label: "2.0 - TT Two",
children: [{
value: 5,
label: "2.1 - TT Two-One",
parent_id: 2,
}]
}
];
function walkRecursive(data) {
let result = []
data.forEach(element => {
let e = { ...element}
result.push(e);
if (e.children) {
let children = walkRecursive(e.children)
result = result.concat(children)
delete e.children
}
});
return result;
}
function walk(data) {
data = data.slice()
let result = []
let d, oldData;
while (d = data.shift()) {
let el = { ...d}
result.push(el)
if (el.children) {
oldData = data
data = el.children.slice();
delete el.children;
} else {
if (oldData && data.length == 0) {
data = oldData
oldData = null;
}
}
}
return result;
}
console.log(walkRecursive(d))
console.log(walk(d))
https://codeburst.io/learn-and-understand-recursion-in-javascript-b588218e87ea

Categories