How to iteratively nest objects within an object - javascript

I have an array that looks like this:
const arr = [
{
parent: 'A',
children: ['B'],
},
{
parent: 'B',
children: ['C'],
},
{
parent: 'C',
children: ['D']
}];
and I want to create a function that will take this array and result in the following object:
const result = {
parent: 'A',
children: [{
parent: 'B',
children: [{
parent: 'C',
children: [{
parent: 'D',
children: []
}]
}]
}]
};
so the result type would look like:
type Result = {
parent: string;
children: Result[];
};
What I've tried so far:
type TInput = {
parent: string;
children: string[];
};
type Result = {
parent: string;
children: Result[];
};
// can assume we know initial parent is 'A'
const fn = (parent: string, inputArr: TInput[]) => {
const result: TResult[] = [];
let newParent: string[] = [];
while (newParent.length !== 0) {
const index = inputArr.findIndex(
(input) => input.parent === parent
);
result.push({
parent: inputArr[index].parent,
children: [], // need to populate on next pass?
});
newParent = inputArr[index].children;
}
return result;
};
I don't know how many objects will be in the input array, but can assume first object is known to be initial parent/child ('A' in the example). Any help much appreciated.
Thanks

I'd use a parent map to recreate children attribute as arrays of parent objects:
const arr = [
{
parent: 'A',
children: ['B'],
},
{
parent: 'B',
children: ['C'],
},
{
parent: 'C',
children: ['D']
},
{
parent: 'D',
children: []
}
];
const makeTree = (manyParents,rootName) => {
// copy parent objects into a map.
let mapIt = new Map(manyParents.map(pObject => {
return [pObject.parent, pObject];
}));
// recreate children arrays for each parents.
mapIt.forEach((oneParent) => {
let newChildrenArray = [];
//find every children objects.
oneParent.children.forEach((oneChild) => {
newChildrenArray.push(mapIt.get(oneChild));
});
//replace children array.
oneParent.children = newChildrenArray;
});
return mapIt.get(rootName);
}
let tree = makeTree(arr,'A');
console.log(tree)

You can use Array#reverse and Array#reduce methods as follows:
const
arr = [ { parent: 'A', children: ['B'], }, { parent: 'B', children: ['C'], }, { parent: 'C', children: ['D'] }],
output = arr.reverse().reduce(
(acc,{parent,children}) =>
!acc.parent ?
({parent,children:children.map( ([parent]) => ({parent,children:[]}) )}):
({parent,children:[acc]}),{}
);
console.log( output );

This seems to do the job :
This is the ts version :
// here i just copy your data
const data = [{
parent: 'A',
children: ['B'],
},
{
parent: 'C',
children: ['D']
},
{
parent: 'B',
children: ['C'],
}
];
const expectedResult = {
parent: 'A',
children: [{
parent: 'B',
children: [{
parent: 'C',
children: [{
parent: 'D',
children: []
}]
}]
}]
};
type TInput = {
parent: string;
children: string[];
};
type TResult = {
parent: string;
children: TResult[];
};
// there is the function that takes an input (the parent element) and an array (all the children)
const parseArray = (obj: TInput, arr: TInput[]): TResult => {
return {
parent: obj.parent,
// if the children exists on the array, we use it, else we create an empty one, which will be used to recusivly generate the tree
children: obj.children.map(child => data.find(e => e.parent === child) ?? {
parent: child,
children: []
}).map(e => parseArray(e, arr))
}
}
// we get the root obj (as you said the first el)
const root = data.shift()!
// and we call the function
console.log(parseArray(root, data))
// we verify that the objects are the same using json (directly using == on objects compares their locations on the disk)
console.log(JSON.stringify(parseArray(root, data)) === JSON.stringify(expectedResult))
And this is the snipped (we can't run ts directly on snippets) :
// here i just copy your data
const data = [{
parent: 'A',
children: ['B'],
},
{
parent: 'C',
children: ['D']
},
{
parent: 'B',
children: ['C'],
}
];
const expectedResult = {
parent: 'A',
children: [{
parent: 'B',
children: [{
parent: 'C',
children: [{
parent: 'D',
children: []
}]
}]
}]
};
// there is the function that takes an input (the parent element) and an array (all the children)
const parseArray = (obj, arr) => {
return {
parent: obj.parent,
// if the children exists on the array, we use it, else we create an empty one, which will be used to recusivly generate the tree
children: obj.children.map(child => data.find(e => e.parent === child) ?? {
parent: child,
children: []
}).map(e => parseArray(e, arr))
}
}
// we get the root obj (as you said the first el)
const root = data.shift()
// and we call the function
console.log(parseArray(root, data))
// we verify that the objects are the same using json (directly using == on objects compares their locations on the disk)
console.log(JSON.stringify(parseArray(root, data)) === JSON.stringify(expectedResult))

Related

Running trough every instance of a muti-layer object in JavaScript

I have an object that contains the following object template:
0:{
id: 1,
name: Name 1
children: 0:{
id: 2,
name: Name 2
children: {}
},
1:{
id: 3,
name: Name 3
children: {}
}
},
1:{
id: 4,
name: Name 4
children: 0:{
id: 5,
name: Name 5
children: {}
}
},
However as you can see every "children" key can also have an exact copy of an object, an so can the children of these children and so on. How can I convert every instance of "children", including the children inside the children and so on, into an array of objects like the example below?
0:{
id: 1,
name: Name 1
children: [
{
id: 2,
name: Name 2
children: {}
},
{
id: 3,
name: Name 3
children: {}
}
]
},
1:{
id: 4,
name: Name 4
children: [
{
id: 5,
name: Name 5
children: {}
}
]
},
Something like this should work:
function flatObject(arg) {
const result = [];
const goTo = (o, index = 0) => {
// If no more objects, return
if (typeof o[index] === "undefined")
return;
const e = o[index];
// Push this object
result.push({ id: e.id, name: e.name });
// goto childrens and append them
goTo(e.children);
// Goto next item
goTo(o, index + 1);
}
// Call function
goTo(arg);
return result;
}
It uses a recursive function to go through each item, and it goes through each children key in order to append them in the result array.
EDIT FOR MODIFIED QUESTION
What you are looking for is the Object.entries() method, that according to the MDN
The Object.entries() method returns an array of a given object's own enumerable string-keyed property [key, value] pairs.
function flatObject(arg) {
// Iterate all
Object.keys(arg).forEach(key => {
const ObjectToArray = (o) => {
// Convert children to array
o.children = Object.entries(o.children).map(e => e[1]);
// Convert children of children
o.children.forEach(child => ObjectToArray(child));
};
// Push childrens
ObjectToArray(arg[key]);
});
return arg;
}

Can we get an object from single object key value in javascript?

I have an array of object - like this -
test: [
{
id:'1',
name:'A'
},
{
id:'2',
name:'B'
},
]
Suppose I have a value 2 that exists in object Test as id. I want to get whole object from array if id value exists in whole array
input - 2,
expected output - {id:'2' , name:'B'}
How Can we get it ? is it any possible solution ?
Simply use find-
const val = [
{
id: '1',
name: 'A',
},
{
id: '2',
name: 'B',
},
];
const res = val.find(obj => obj.id === '2');
console.log(res);
There can be multiple ways to do this. Here is how I did it.
let test = [
{
id: '1',
name: 'A'
},
{
id: '2',
name: 'B'
}
];
let result = (param) => test.filter(el => {
return el.id == param
});
console.log(result(2))

Looking to group array by values in the sub array

Trying to parse one data set that has a bunch of the same "secondaryIDs" in way that i can group and iterate through them together.
In english what im trying to do is
"select a unique group of all items where the value of field is unique "
'use strict';
const data = [{
Group: 'A',
Name: 'SD'
}, {
Group: 'B',
Name: 'FI'
}, {
Group: 'A',
Name: 'MM'
}, {
Group: 'B',
Name: 'CO'
}];
let unique = [...new Set(data.map(item => item.Group))];
console.log(unique);
Which gives ["A"],["B"]
but what im looking for is
{
A: [ "SD","MM" ],
B: [ "FI","CO" ],
}
For this, I would use array.reduce instead of array.map because what you're actually hoping to return is a new value, not a modified array, the reduce method is perfect when you want to literally reduce the array into a single output value, in your case an object of unique groups. Maybe try something like this:
let unique = data.reduce((acc, { Group, Name }) => {
if (!(acc.hasOwnProperty(Group))) {
acc[Group] = [Name];
} else {
acc[Group].push(Name);
};
return acc;
}, {});
I've also added a pen for this at: https://codepen.io/anon/pen/BGpgdz?editors=1011 so you can see this working.
Hope this helps!
You can also reduce your array to the grouped object (keyed by Group values):
const data = [{
Group: 'A',
Name: 'SD'
}, {
Group: 'B',
Name: 'FI'
}, {
Group: 'A',
Name: 'MM'
}, {
Group: 'B',
Name: 'CO'
}];
const grouped = data.reduce((a, {Group, Name}) => {
if (!(Group in a)) a[Group] = [Name];
else a[Group].push(Name);
return a;
}, {});
console.log(grouped);
can do something like..
const map = {};
data.forEach( d => {
if( map[d.Group] ) {
map[d.Group].push(d.Name);
} else {
map[d.Group] = [d.Name];
}
})
console.log(map)
I think the easiest way to achieve this would be to use Array.prototype.reduce method to create an object that maps unique Group names to arrays that contain Names. You can supply an empty object literal as your initial reduce accumulator:
const data = [{
Group: 'A',
Name: 'SD'
}, {
Group: 'B',
Name: 'FI'
}, {
Group: 'A',
Name: 'MM'
}, {
Group: 'B',
Name: 'CO'
}];
var namesByGroup = data.reduce((map, el) => {
map[el.Group] ? map[el.Group].push(el.Name) : map[el.Group] = [el.Name];
return map;
}, {});
console.log(namesByGroup);
If you're interested in a functional approach, here is a solution using Ramda:
const group =
R.pipe(
R.groupBy(R.prop('Group')),
R.map(R.map(R.prop('Name'))));
console.log(
group([
{Group: 'A', Name: 'SD'},
{Group: 'B', Name: 'FI'},
{Group: 'A', Name: 'MM'},
{Group: 'B', Name: 'CO'}])
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>
can also be done using forEach
const data = [{
Group: 'A',
Name: 'SD'
}, {
Group: 'B',
Name: 'FI'
}, {
Group: 'A',
Name: 'MM'
}, {
Group: 'B',
Name: 'CO'
}];
const somefunction = (data) => {
let arr = {}
data.forEach( ({Group, Name}) => {
Group in arr ? arr[Group].push(Name) : arr[Group] = [Name]
})
return arr;
}
console.log(somefunction(data))

javascript find deeply nested objects

I need to filter objects recursively in a deeply nested array of objects using javascript, maybe with the help of lodash.
What is the cleanest way to do it, If I don't know how many nested object there will be in my array?
Let's say I have the following structure
[
{
label: "first",
id: 1,
children: []
},
{
label: "second",
id: 2,
children: [
{
label: "third",
id: 3,
children: [
{
label: "fifth",
id: 5,
children: []
},
{
label: "sixth",
id: 6,
children: [
{
label: "seventh",
id: 7,
children: []
}
]
}
]
},
{
label: "fourth",
id: 4,
children: []
}
]
}
];
I want to find the one with id 6, and if it has children return true otherwise false.
Of course If I have a similar data structure but with different number of items it should work too.
Since you only want a true of false answer you can use some() on the recursion, effectively doing a depth-first search, and make it pretty succinct:
let arr = [{label: "first",id: 1,children: []},{label: "second",id: 2,children: [{label: "third",id: 3,children: [{label: "fifth",id: 5,children: []},{label: "sixth",id: 6,children: [{label: "seventh",id: 7,children: []}]}]},{label: "fourth",id: 4,children: []}]}];
function findNested(arr, id) {
let found = arr.find(node => node.id === id)
return found
? found.children.length > 0
: arr.some((c) => findNested(c.children, id))
}
console.log(findNested(arr, 6)) // True: found with children
console.log(findNested(arr, 7)) // False: found no children
console.log(findNested(arr, 97)) // False: not found
Perhaps a recursive solution along the lines of this might work for you? Here, the node with supplied id is recursively searched for through the 'children' of the supplied input data. If a child node with matching id is found, a boolean result is returned based on the existence of data in that nodes children array:
function nodeWithIdHasChildren(children, id) {
for(const child of children) {
// If this child node matches supplied id, then check to see if
// it has data in it's children array and return true/false accordinly
if(child.id === id) {
if(Array.isArray(child.children) && child.children.length > 0) {
return true
}
else {
return false
}
}
else {
const result = nodeWithIdHasChildren(child.children, id);
// If result returned from this recursion branch is not undefined
// then assume it's true or false from a node matching the supplied
// id. Pass the return result up the call stack
if(result !== undefined) {
return result
}
}
}
}
const data = [
{
label: "first",
id: 1,
children: []
},
{
label: "second",
id: 2,
children: [
{
label: "third",
id: 3,
children: [
{
label: "fifth",
id: 5,
children: []
},
{
label: "sixth",
id: 6,
children: [
{
label: "seventh",
id: 7,
children: []
}
]
}
]
},
{
label: "fourth",
id: 4,
children: []
}
]
}
];
console.log('node 6 has children:', nodeWithIdHasChildren( data, 6 ) )
console.log('node 7 has children:', nodeWithIdHasChildren( data, 7 ) )
console.log('node 100 has children:', nodeWithIdHasChildren( data, 7 ), '(because node 100 does not exist)' )
Here is another solution using recursion and doing it via only one Array.find:
const data = [ { label: "first", id: 1, children: [] }, { label: "second", id: 2, children: [ { label: "third", id: 3, children: [ { label: "fifth", id: 5, children: [] }, { label: "sixth", id: 6, children: [ { label: "seventh", id: 7, children: [] } ] } ] }, { label: "fourth", id: 4, children: [] } ] } ];
const search = (data, id) => {
var f, s = (d, id) => d.find(x => x.id == id ? f = x : s(x.children, id))
s(data, id)
return f ? f.children.length > 0 : false
}
console.log(search(data, 6)) // True: found with children
console.log(search(data, 7)) // False: found but has no children
console.log(search(data, 15)) // False: not found at all
The idea is to have a recursive function which when finds the id remembers the object.
Once we have the found (or we know we do not have an entry found) just return the children array length or return false.
If you want to actually return the found object instead of the boolean for children.length:
const data = [ { label: "first", id: 1, children: [] }, { label: "second", id: 2, children: [ { label: "third", id: 3, children: [ { label: "fifth", id: 5, children: [] }, { label: "sixth", id: 6, children: [ { label: "seventh", id: 7, children: [] } ] } ] }, { label: "fourth", id: 4, children: [] } ] } ];
const search = (data, id) => {
var f, s = (d, id) => d.find(x => x.id == id ? f = x : s(x.children, id))
s(data, id)
return f
}
console.log(search(data, 6)) // returns only the object with id:6
console.log(search(data, 7)) // returns only the object with id: 7
console.log(search(data, 71)) // returns undefined since nothing was found
You can use "recursion" like below to check if id has children or not
let arr = [{label: "first",id: 1,children: []},{label: "second",id: 2,children: [{label: "third",id: 3,children: [{label: "fifth",id: 5,children: []},{label: "sixth",id: 6,children: [{label: "seventh",id: 7,children: []}]}]},{label: "fourth",id: 4,children: []}]}];
function hasChildren(arr, id) {
let res = false
for (let d of arr) {
if(d.id == id) return d.children.length > 0
res = res || hasChildren(d.children, id)
if(res) return true
}
return res
}
console.log('id 4 has children? ', hasChildren(arr, 4))
console.log('id 6 has children? ', hasChildren(arr, 6))
You can do it using three simple javascript functions:
// Function to Flatten results
var flattenAll = function(data) {
var result = [];
var flatten = function(arr) {
_.forEach(arr, function(a) {
result.push(a);
flatten(a.children);
});
};
flatten(data);
return result;
};
// Function to search on flattened array
var search = function(flattened, id) {
var found = _.find(flattened, function(d) {
return d.id == id;
});
return found;
};
// Function to check if element is found and have children
var hasChildren = function(element) {
return element && element.children && element.children.length > 0;
}
// Usage, search for id = 6
hasChildren(search(flattenAll(your_data_object), 6))
Plunker
You can use a generator function to iterate the nodes recursively and simplify your logic for checking existence by using Array.prototype.some():
const data = [{label:'first',id:1,children:[]},{label:'second',id:2,children:[{label:'third',id:3,children:[{label:'fifth',id:5,children:[]},{label:'sixth',id:6,children:[{label:'seventh',id:7,children:[]}]}]},{label:'fourth',id:4,children:[]}]}];
function * nodes (array) {
for (const node of array) {
yield node;
yield * nodes(node.children);
}
}
const array = Array.from(nodes(data));
console.log(array.some(node => node.id === 6 && node.children.length > 0));
console.log(array.some(node => node.id === 7 && node.children.length > 0));
The JSON.parse reviver parameter or the JSON.stringify replacer parameter can be used to check all values, and generate flat id lookup object with references to the nodes :
var lookup = {}, json = '[{"label":"first","id":1,"children":[]},{"label":"second","id":2,"children":[{"label":"third","id":3,"children":[{"label":"fifth","id":5,"children":[]},{"label":"sixth","id":6,"children":[{"label":"seventh","id":7,"children":[]}]}]},{"label":"fourth","id":4,"children":[]}]}]'
var result = JSON.parse(json, (key, val) => val.id ? lookup[val.id] = val : val);
console.log( 'id: 2, children count:', lookup[2].children.length )
console.log( 'id: 6, children count:', lookup[6].children.length )
console.log( lookup )
I suggest to use deepdash extension for lodash:
var id6HasChildren = _.filterDeep(obj,
function(value, key, parent) {
if (key == 'children' && parent.id == 6 && value.length) return true;
},
{ leavesOnly: false }
).length>0;
Here is a docs for filterDeep.
And this a full test for your case.
We now use object-scan for data processing needs like this. It's very powerful once you wrap your head around it. This is how you could solve your questions
// const objectScan = require('object-scan');
const hasChildren = (e) => e instanceof Object && Array.isArray(e.children) && e.children.length !== 0;
const find = (id, input) => {
const match = objectScan(['**'], {
abort: true,
rtn: 'value',
filterFn: ({ value }) => value.id === id
})(input);
return hasChildren(match);
};
const data = [{ label: 'first', id: 1, children: [] }, { label: 'second', id: 2, children: [{ label: 'third', id: 3, children: [{ label: 'fifth', id: 5, children: [] }, { label: 'sixth', id: 6, children: [{ label: 'seventh', id: 7, children: [] }] }] }, { label: 'fourth', id: 4, children: [] }] }];
console.log(find(6, data));
// => true
console.log(find(2, data));
// => true
console.log(find(7, data));
// => false
console.log(find(999, data));
// => false
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.8.0"></script>
Disclaimer: I'm the author of object-scan

JS Tree search fails

I have Javascript tree structure
const tree = [
{id: 120 , children:[]},
{id: 110 , children:[
{id: 12 , children:[
{id: 3 , children:[]},
{id: 4 , children:[]}
]}
]},
{id: 10 , children:[
{id: 13 , children:[]}
]}
]
and i have this function to find the parent of given node
const _findParent = (tree, component, _parent) => {
let parent = null
// iterate
tree.forEach(node => {
if(node.id === component.id){
return _parent
}
parent = node. children ? _findParent(node.children, component, node) : parent
})
return parent
}
but it returns null, I cant find where i miss the parent object.
Basically you check the children, but your children is always an array which is a truthy value. In this case, you could check if children is an array.
The use of Array#forEach does not work with a return value for using outside of the callback.
I suggest to use Array#some, which allows an early exit, which is necessary if a node is found.
Then I suggest to use a second variable for getting a nested result and if truthy, then assign to parent for return and exit the iteration.
const _findParent = (tree, component, _parent) => {
let parent;
tree.some(node => {
var temp;
if (node.id === component.id) {
parent = _parent;
return true;
}
temp = Array.isArray(node.children) && _findParent(node.children, component, node);
if (temp) {
parent = temp;
return true;
}
});
return parent;
}
const tree = [{ id: 120, children: [] }, { id: 110, children: [{ id: 12, children: [{ id: 3, children: [] }, { id: 4, children: [] }] }] }, { id: 10, children: [{ id: 13, children: [] }] }];
console.log(_findParent(tree, { id: 4 }));
console.log(_findParent(tree, { id: 42 }));

Categories