Running trough every instance of a muti-layer object in JavaScript - 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;
}

Related

Array of arrays of objects - find object value occurance and return true/false (js)

I have array of array of object as follows:
[
[
{
id: 1,
itemName: 'xxx',
...
},
{
id: 1,
itemName: 'yyy',
...
},
...
],
[
{
id: 2,
itemName: 'aaa',
...
},
{
id: 2,
itemName: 'kkk',
...
},
...
],
[
{
id: 3,
itemName: 'kkk',
...
},
{
id: 3,
itemName: 'yyy',
...
},
...
]
]
I am trying to check if any itemName from objects inside arrays equals given string, but I stuck at the solution that keeps these arrays with such object in one array. Here is my solution:
function isNameAcrossData(givenString){
return arr.map(arrItem =>
arrItem.find(item => item.itemId === givenString)
);
}
My solution doesn't return boolean but just one array with objects, that contain givenString and undefined as last array element. How to modify it to return just true/false value?
Use a .some inside a .some, to see if some of the arrays have at least one element inside matching the condition:
const isNameAcrossData = givenString => arr.some(
subarr => subarr.some(
({ itemName }) => itemName === givenString
)
);
const arr=[[{id:1,itemName:"xxx"},{id:1,itemName:"yyy"}],[{id:2,itemName:"aaa"},{id:2,itemName:"kkk"}],[{id:3,itemName:"kkk"},{id:3,itemName:"yyy"}]];
console.log(isNameAcrossData('xxx'));
console.log(isNameAcrossData('doesntexist'));
You could also flatten the outer array first:
const isNameAcrossData = givenString => arr.flat().some(
({ itemName }) => itemName === givenString
);
const arr=[[{id:1,itemName:"xxx"},{id:1,itemName:"yyy"}],[{id:2,itemName:"aaa"},{id:2,itemName:"kkk"}],[{id:3,itemName:"kkk"},{id:3,itemName:"yyy"}]];
console.log(isNameAcrossData('xxx'));
console.log(isNameAcrossData('doesntexist'));
You could check with some and return an array of boolean with using the wanted property.
function mapHasValue(key, value) {
return data.map(array => array.some(item => item[key] === value));
}
var data = [[{ id: 1, itemName: 'xxx' }, { id: 1, itemName: 'yyy' }], [{ id: 2, itemName: 'aaa' }, { id: 2, itemName: 'kkk' }], [{ id: 3, itemName: 'kkk' }, { id: 3, itemName: 'yyy' }]];
console.log(mapHasValue('id', 3));
Your code returns
[undefined, undefined, undefined]
because map returns an array so this approach won't work
You have first to loop through all the data and check inside then outside the loop assign to some variable true if there is a match.
Basically you have to return after you loop the data.
Working example for both cases:
const arr=[[{id:1,itemName:"xxx"},{id:1,itemName:"yyy"}],[{id:2,itemName:"aaa"},{id:2,itemName:"kkk"}],[{id:3,itemName:"kkk"},{id:3,itemName:"yyy"}]];
function isNameAcrossData(givenString){
let isMatch = false;
arr.map(childArr => {
childArr.map(obj => obj.itemName === givenString ? isMatch = true : null);
});
return isMatch;
}
console.log(isNameAcrossData('kkk'));
console.log(isNameAcrossData('bbb'));

how to calculate a sum of an array in an object

I had a variable like that
const data = {
code: 1,
items: [
{ nickname: 1, name: [
{id : "A"},
{id : "B"}
]
},
{
nickname: 2, name: [
{id: "A"},
{id: "C"}
]
}
]
}
after that, I want to show how many characters: A:2, B:1, C:1
You can do that is following steps:
Use flatMap() on the array data.items
Inside flatMap() use map() to convert all the object to their id and return it from flatMap(). This way you will array ["A","B","A","C"]
Then use reduce() and get an object with count of all the letters.
const data = { code: 1, items: [ { nickname: 1, name: [ {id : "A"}, {id : "B"} ] }, { nickname: 2, name: [ {id: "A"}, {id: "C"} ] } ] }
const res = data.items.flatMap(x =>
x.name.map(a => a.id)
).reduce((ac,a) => (ac[a] = ac[a] + 1 || 1,ac),{});
console.log(res)
const data = {
code: 1,
items: [
{
nickname: 1,
name: [
{ id: "A" },
{ id: "B" }
]
},
{
nickname: 2,
name: [
{ id: "A" },
{ id: "C" }
]
}
]
};
const res = data.items.reduce((acc, next) => {
next.name.forEach(({ id }) => {
acc[id] = acc[id] + 1 || 1;
});
return acc;
}, {});
console.log(res);
You can do that in a single shot using reduce.
Reducing data.items will allow you to add to the accumulator (initially an empty object), the value of the currently looped name property item.
The result will be an object owning all the occurences of each encountered letter in the name property of each array.
Relevant lines explained:
data.items.reduce((acc, next) will call the reduce method on data.items. acc is the reduce accumulator (initially an empty object), next is the currently looped item of data.items.
next.name.forEach(({id}) in this line, we loop the name property of the currently looped item (data.items[n]). ({id}) is a short syntax to acquire the id property of the looped item in the foreach. It's equivalent to (item => item.id).
acc[id] = acc[id] + 1 || 1; tries to increase the property [id] of the accumulator (example: "A" of {}) by 1. If it does not exist, it sets the value to 1.
return acc; returns the accumulator.
You could iterate name and take id in a loop for assigning the count.
const
data = { code: 1, items: [{ nickname: 1, name: [{ id : "A" }, { id : "B" }] }, { nickname: 2, name: [{ id: "A" }, { id: "C" }] }] },
result = data.items.reduce(
(r, { name }) => (name.forEach(({ id }) => r[id] = (r[id] || 0 ) + 1), r),
{}
);
console.log(result);

Immutable.js algorithm: List.update_or_add(item)

I want to concatenate 2 lists in immutable.js.
Both lists have this structure: { id, value }
The algorithm concatenate should do this:
If an ID exists in both list1 and list2 take the value from list2.
let list1 = [
{ id: 1, value: 'foo' },
{ id: 3, value: 'bar' },
{ id: 2, value: 'baz' },
]
let list2 = [
{ id: 1, value: 'quux' }, // id 1 exists in list1
{ id: 4, value: 'asd' },
]
let result = [
{ id: 1, value: 'quux' }, // from list 2
{ id: 3, value: 'bar' },
{ id: 2, value: 'baz' },
{ id: 4, value: 'asd' },
]
If Immutable.js has this functionality with another type (eg. Dictionary), I could also use that.
Algorithms for union
First you have to maintain two map with key as id and value as object then check for length of array which is of bigger size and pass the bigger size array with small size map to merged function there you can iterate over the array and check if it's exists in the map if yes then update the object and delete that row from map otherwise add the object into output. After the for loop complete check if map has element present then push all the values from map into output array and return;
index.js
const old = [
{ id: 1, value: 'foo' },
{ id: 3, value: 'bar' },
{ id: 2, value: 'baz' },
];
const newa = [
{ id: 1, value: 'quux' }, // update
{ id: 4, value: 'asd' }, // push
];
function merged(input,filterMap){
var output = [];
input.forEach(function(eachRow){
if(filterMap.hasOwnProperty(eachRow.id)){
output.push(Object.assign(eachRow,filterMap[eachRow.id]));
delete filterMap[eachRow.id];
}else{
output.push(eachRow);
}
});
if(Object.keys(filterMap).length > 0){
output = output.concat(Object.values(filterMap));
}
return output;
}
function parseData(first,second){
var mapFirst = {},
mapSecond = {};
var output = [];
first.forEach(function(eachRow){
mapFirst[eachRow.id] = eachRow;
});
second.forEach(function(eachRow){
mapSecond[eachRow.id] = eachRow;
});
if(first.length > second.length){
return merged(first,mapSecond);
}else{
return merged(second,mapFirst);
}
}
console.log(parseData(old,newa));
Working jsFiddle demo - https://jsfiddle.net/qz25hnmf/

Lodash - Filter nested collection by array of prop value

Say the original collection is:
var data = [{
id: 1,
children: [{
id: 2
}, {
id: 3
}]
}, {
id: 4,
children: [{
id: 5
}]
}]
Want to filter it with this given values of id property:
var selectedIds = [1, 3, 4]
The results should be:
var result = [{
id: 1,
children: [{
id: 3
}]
}, {
id: 4,
children: []
}]
How to accomplish this using Lodash methods?
You can use map and filter together to apply filter on nested element,
_.map(data, elem => {
elem.children = _.filter(elem.children, child => selectedIds.includes(child.id));
return elem;
});
NOTE: Assuming the filter has to be applied only on children property.
To not modify the original data,
_.map(data, elem => {
let children = _.filter(elem.children, child => selectedIds.includes(child.id));
return Object.assign({}, elem, {children});
});
You'll need to recursively walk your collection (and each item's children). I don't believe lodash has a native way of implementing this. I wrote a function called matcher that takes the collection and selected ids, and returns items that match those ids, and passes the children in and does the same thing. I wasn't sure how to handle a situation where a parent isn't selected but the children are.... I just assumed you'd throw them all away.
Point is, you have a very particular structure (items and children that are items) that requires you to write a specific recursive function that knows how to walk that structure.
const matcher = (collection, selected) => _.reduce(collection, (result, item) => {
if (selected.includes(item.id)) {
result.push({
id: item.id,
children: matcher(item.children, selected)
})
}
return result
}, [])
const result = matcher(data, selectedIds)
You could check for id and take only children with wanted id.
function getParts(array, ids) {
return array.reduce((r, { id, children }) =>
r.concat(ids.includes(id)
? { id, children: getParts(children || [], ids) }
: []
), []);
}
var data = [{ id: 1, children: [{ id: 2 }, { id: 3 }] }, { id: 4, children: [{ id: 5 }] }];
console.log(getParts(data, [1, 3, 4]));
console.log(getParts(data, [1, 3, 5]));
.as-console-wrapper { max-height: 100% !important; top: 0; }

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