Javascript get full index of nested object - javascript

const items = [
{ id: 'item1',
children: [
{ id: 'item1-1',
children: [
{ id: 'item1-1-1' },
{ id: 'item1-1-2' },
{ id: 'item1-1-3'
children: [
{ id: 'item1-1-3-1'}
]
},
]
},
{ id: 'item1-2',
children: [
{ id: 'item1-2-1' }
]
}
]
},
{ id: 'item2' }
]
What I want to is like below,
function getFullDepthOfObject(){
...
}
getFullIndexOfObject('item1') =====> return '1'
getFullIndexOfObject('item1-2') =====> return '1-2'
getFullIndexOfObject('item1-1-1') =====> return '1-1-1'
getFullIndexOfObject('item1-1-2') =====> return '1-1-2'
getFullIndexOfObject('item2') ===> return '2'
I have struggled with this too much time, But I couldn't make it. I think I should stack each of parent index, But I don't know how to get its parent. Is there a way to do this?
Not parse of id string. Each id has randomic string. The id like item1-2 is for easier demonstration.
I think my way is too verbose...
I tried like ...
// Get Full Index of item1-1
// First, get the target's depth.
var depth = 0;
function getDepthOfId(object, id) {
var level;
if (object.id === id) return 1;
object.children && object.children.some(o => level = getDepthOfId(o, id));
return level && level + 1;
}
depth = getDepthOfId(items[0], 'item1-1');
console.log('depth === ', depth)
// Then, iterate recursively with length of depth.
var indexStacks = [];
function getNestedIndexOfId(obj, id, index) {
if (obj.id === id) {
indexStacks = [index, ...indexStacks]
return index;
}
if (obj.children) {
depth++;
obj.children.map((child, i) => {
getNestedIndexOfId(child, id, i)
})
}
}
// I can get the inner index, but I can't get its parent id.
// I don't know how to this..
function getParentId(obj, id){
// ...?
var parentId;
return parentId;
}
for(var i=0; i<depth; i++){
getNestedIndexOfId('...')
}
// full path will be
indexStacks.join('-')

const items = [
{ id: 'item1',
children: [
{ id: 'item1-1',
children: [
{ id: 'item1-1-1' },
{ id: 'item1-1-2' },
{ id: 'item1-1-3',
children: [
{ id: 'item1-1-3-1'}
]
}
]
},
{ id: 'item1-2',
children: [
{ id: 'item1-2-1' }
]
}
]
},
{ id: 'item2' }
];
const searchIt = (node, search, path = '', position = 0) => {
if (node.id && node.id === search) {return path !== '' ? `${path}-${position}` : position;}
if (!node.children) {return false}
const index = node.children.findIndex((x) => x.id && x.id === search);
if (index >= 0) {
return path !== '' ? `${path}-${index + 1}` : index + 1;
}
for (let i = 0; i < node.children.length; i++) {
const result = searchIt(node.children[i], search, path !== '' ? `${path}-${i+1}` : i + 1, i);
if (result){
return result;
}
}
return false;
};
console.log(searchIt({children: items}, 'item1-1'));
console.log(searchIt({children: items}, 'item1-1-1'));
console.log(searchIt({children: items}, 'item1-1-2'));
console.log(searchIt({children: items}, 'item1-1-3'));
console.log(searchIt({children: items}, 'item1-1-3-1'));
console.log(searchIt({children: items}, 'item1-2-1'));
console.log(searchIt({children: items}, 'item1-1-3-2'));
console.log(searchIt({children: items}, 'item1-2-2'));
console.log(searchIt({children: items}, 'item3'));

You could take an recursive and iterative approach. On found, the path is returned from the most inner object to the outer call of the function.
function getPath(array, id) {
var result;
array.some((o, i) => {
var temp;
if (o.id === id) return result = `${i + 1}`;
if (temp = getPath(o.children || [], id)) return result = `${i + 1}-${temp}`;
});
return result;
}
const items = [{ id: 'item1', children: [{ id: 'item1-1', children: [{ id: 'item1-1-1' }, { id: 'item1-1-2' }, { id: 'item1-1-3', children: [{ id: 'item1-1-3-1'}] }] }, { id: 'item1-2', children: [{ id: 'item1-2-1' }] }] }, { id: 'item2' }];
console.log(getPath(items, 'item1')); // '1'
console.log(getPath(items, 'item1-2')); // '1-2'
console.log(getPath(items, 'item1-1-1')); // '1-1-1'
console.log(getPath(items, 'item1-1-2')); // '1-1-2'
console.log(getPath(items, 'item2')); // '2'

You can solve this problem using recursion. I have edited my code block and made it into a testable snippet. I had to fix an error in your data (missing comma or something don't remember).
const items = [
{ id: 'itemA',
children: [
{ id: 'item1-1',
children: [
{ id: 'item1-1-1' },
{ id: 'item1-1-2' },
{ id: 'item1-1-3', children: [ { id: 'item1-1-3-1'} ] },
]
},
{ id: 'item1-2', children: [ { id: 'item1-2-1' } ] },
],
},
{ id: 'item2' }
];
const getItemLevel = (targetKey, item, depth = 0) => {
if (item.id === targetKey) return depth;
let foundLevel = null;
if (item.children) {
item.children.forEach((child) => {
if (foundLevel) return;
foundLevel = getItemLevel(targetKey, child, depth +1);
})
}
return foundLevel;
}
console.log(getItemLevel('item1-1-1', { id:'root', children: items }));
console.log(getItemLevel('item2', { id:'root', children: items }));
console.log(getItemLevel('item1-1-3-1', { id:'root', children: items }));
console.log(getItemLevel('keydoesnotexist', { id:'root', children: items }));

a simple way:
const recursiveFind = (arr, id, res = {indexes: [], found: false}) => {
if (!Array.isArray(arr)) return res
const index = arr.findIndex(e => e.id === id)
if (index < 0) {
for (let i = 0; i < arr.length; i++) {
res.indexes.push(i+1)
const childIndexes = recursiveFind(arr[i].children, id, res)
if (childIndexes.found){
return childIndexes
}
}
}
else {
res.found = true
res.indexes.push(index+1)
}
return res
}
recursiveFind(items, 'item1-1-2').indexes.join('-')

If it's ok to use Lodash+Deepdash, then:
let path;
_.eachDeep(items,(val,key,parent,context)=>{
if(path) return false;
if(val.id=='item1-1-2'){
path=context.path;
return false;
}
},{tree:true,pathFormat:'array'});
console.log(_(path).without('children').map(v=>parseInt(v)+1).join('-'));
Here is a codepen for this

Related

How to return the difference between two Objects having array and return the difference value dynamically

Let's say I have two objects of arrays:
const newObj = {
description: "abcd",
type: "anything",
list: [
{ id: 1, name: "Peter" },
{ id: 2, name: "Cathenna" },
{ id: 3, name: "Chiyo" }
]
}
const oldObj = {
description: "wwew",
type: "anything",
list: [
{ id: 1, name: "Amara" },
{ id: 2, name: "shinzo" },
{ id: 3, name: "Chiyo" }
]
}
I want to find all the updated data in newObj objects. Ex, description of oldObj is updated to "abcd" in newObj and in list name of two objects has been updated. So, my expected output is:
const extractObjDiff = {
description: "abcd",
list: [
{ id: 1, name: "Peter" },
{ id: 2, name: "Cathenna" }
]
}
I have tried below code but it's not working for array list.
function extractObjDiff(newObj, oldObj) {
var r = {};
for (var prop in newObj) {
if (!oldObj.hasOwnProperty(prop)) {
if (Array.isArray(newObj)) {
r = newObj;
} else {
r[prop] = newObj[prop];
}
} else if (newObj[prop] === Object(newObj[prop])) {
var difference = newObj[prop].length !== oldObj[prop].length ? newObj[prop] : extractObjDiff(newObj[prop], oldObj[prop], []);
if (Object.keys(difference).length > 0) r[prop] = difference;
} else if (newObj[prop] !== oldObj[prop]) {
if (newObj[prop] === undefined)
r[prop] = 'undefined';
if (newObj[prop] === null)
r[prop] = null;
else if (typeof newObj[prop] === 'function')
r[prop] = 'function';
else if (typeof newObj[prop] === 'object')
r[prop] = 'object'
else if (Array.isArray(newObj))
r = newObj;
else
r[prop] = newObj[prop];
}
}
return r;
}
I have added 2 functions, first one give your expected output and second one give the every changes in the new object.
const newObj = {
description: "abcd",
type: "anything",
list: [
{ id: 1, name: "Peter" },
{ id: 2, name: "Cathenna" },
{ id: 3, name: "Chiyo" }
]
};
const oldObj = {
description: "wwew",
type: "anything",
list: [
{ id: 1, name: "Amara" },
{ id: 2, name: "shinzo" },
{ id: 3, name: "Chiyo" }
]
};
const findSubChanges = (oldObj, newObj) => {
let changes=[]
for (const i in oldObj) {
if (oldObj[i] instanceof Object) {
let isChange=false
for (const j in oldObj[i]){
if(oldObj[i][j] !== newObj[i][j]){
isChange=true
break
}
}
if (isChange){
changes[i] = newObj[i]
}
} else {
if (oldObj[i] !== newObj[i]) {
changes[i] = newObj[i];
}
}
}
return changes
};
const findChanges = (oldObj, newObj) => {
let changes={}
for (const i in oldObj) {
if (oldObj[i] instanceof Object) {
const change = findSubChanges(oldObj[i], newObj[i]);
if (change) {
changes[i] = change
}
} else {
if (oldObj[i] !== newObj[i]) {
changes[i] = newObj[i];
}
}
}
return changes
};
const extractObjDiff=findChanges(oldObj, newObj)
console.log(extractObjDiff);
const newObj = {
description: "abcd",
type: "anything",
list: [
{ id: 1, name: "Peter" },
{ id: 2, name: "Cathenna" },
{ id: 3, name: "Chiyo" }
]
};
const oldObj = {
description: "wwew",
type: "anything",
list: [
{ id: 1, name: "Amara" },
{ id: 2, name: "shinzo" },
{ id: 3, name: "Chiyo" }
]
};
const isArray = (item) => item instanceof Array;
const isJSONObject = (item) =>
item instanceof Object && !(item instanceof Array);
const findEveryChanges = (oldObj, newObj) => {
if (
(isArray(oldObj) && isArray(newObj)) ||
(isJSONObject(oldObj) && isJSONObject(newObj))
) {
let changes = isArray(oldObj) ? [] : {};
for (const i in oldObj) {
if (oldObj[i] instanceof Object) {
const change = findEveryChanges(oldObj[i], newObj[i]);
if (change) {
changes[i] = change;
}
} else {
if (oldObj[i] !== newObj[i]) {
changes[i] = newObj[i];
}
}
}
if (Object.keys(changes).length > 0) {
return changes;
}
return;
}
if (oldObj !== newObj) {
return newObj;
}
return;
};
console.log(findEveryChanges(oldObj, newObj));

How to find the count of some object property in array of objects using react and javascript?

Hi below is the array of objects.
const arr_obj = [
{
id: '1',
children: [],
type: 'TYPE1',
},
{
id: '2',
children: [
{
id: '1',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
{
id: '2',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
{
id: '3',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
]
type: 'TYPE2',
},
{
id: '3',
children: [
{
id: '4',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
{
id: '5',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
{
id: '6',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
]
type: 'TYPE2',
}
]
I have to find out the count of type: 'MAIN'. these 'MAIN' will be within type: "type2"
So the expected count is 6. The outer children array can be empty and sometimes inner children array with type: "type2" is not there at all examples like below:
children: [] //empty array
children: [
{
id: '1',
children: [],
type: 'TYPE2',
},
] //no children with type: 'TYPE2'
below is the code to handle above,
const findCount = (arr_obj) => {
let count = 0;
const expectedCount = 2;
const loop = (children) => {
for (const obj of children) {
const { type, children } = obj;
if (type === 'TYPE2') {
loop(children);
} else if (type === 'MAIN') {
++count;
}
}
};
loop(children);
return count > expectedCount;
};
const output = findCount(arr_obj);
the above works fine. but it doesnt handle case when inner children is [] like below,
children: [
{
id: '1',
children: [],
type: 'TYPE2',
},
] //no children for type: 'TYPE2'
how can i handle the above data with no inner children array for children of type "TYPE2". could someone help me with this. thanks.
how can i handle the above data with no inner children array for children of type "TYPE2".
You could add a guard clause to your loop function:
if (!children) return;
Which returns directly if there are no children.
Resulting in:
const findCount = (arr_obj) => {
let count = 0;
const expectedCount = 2;
const loop = (children) => {
if (!children) return;
for (const obj of children) {
const { type, children } = obj;
if (type === 'TYPE2') {
loop(children);
} else if (type === 'MAIN') {
++count;
}
}
};
loop(arr_obj); // <- this should probably refer to `arr_obj`
return count > expectedCount;
};
const output = findCount(arr_obj);
For no inner children, you can check if the array of children has any elements in it or not. Something like this
const loop = (children) => {
for (const obj of children) {
const { type, children } = obj;
if (type === 'TYPE2' && children.length > 0) {
loop(children);
} else if (type === 'MAIN') {
++count;
}
}
};
let count = 0;
arr_obj.?.filter(item=> item.type =="TYPE2").forEach((item)=> {
if(item.children.length > 0){
item.children.forEach((childItem)=>{
if(childItem.type == "MAIN"){
count+=1;
}
})
}
})
console.log(count);// output will be 6
I modified the example by adding another valid occurrence in the nested object.
The following would also work.
// count the occurences of typeX as the very next of typeY
const count_TypeX_Within_TypeY = (arr, typeX, typeY) => {
let count = 0;
arr.forEach((item) => {
// check the type in current level
if (item.type === typeY) {
item.children.forEach((innerItem) => {
// // check the type in next level
if (innerItem.type === typeX) {
count += 1;
}
});
}
// do the same recursively
count += count_TypeX_Within_TypeY(item.children || [], typeX, typeY);
});
return count;
};
const arr_obj = [{"id":"1","children":[],"type":"TYPE1"},{"id":"2","children":[{"id":"1","children":[{}],"type":"MAIN"},{"id":"2","children":[{}],"type":"MAIN"},{"id":"3","children":[{}],"type":"MAIN"}],"type":"TYPE2"},{"id":"3","children":[{"id":"4","children":[{}],"type":"MAIN"},{"id":"5","children":[{"id":"7","type":"TYPE2","children":[{"id":"8","type":"MAIN","children":[{}]}]}],"type":"MAIN"},{"id":"6","children":[{}],"type":"MAIN"}],"type":"TYPE2"}];
console.log(count_TypeX_Within_TypeY(arr_obj, "MAIN", "TYPE2"));

Creating a Tree out of PathStrings

I have a similar problem to this (Get a tree like structure out of path string). I tried to use the provided solution but can not get it to work in Angular.
The idea is to the separate incoming path strings (see below) and add them to an object and display them as a tree.
pathStrings: string[] = [
"PathA/PathA_0",
"PathA/PathA_1",
"PathA/PathA_2/a",
"PathA/PathA_2/b",
"PathA/PathA_2/c"
];
let tree: Node[] = [];
for (let i = 0; i < this.pathStrings.length; i++) {
tree = this.addToTree(tree, this.pathStrings[i].split("/"));
}
addToTree(root: Node[], names: string[]) {
let i: number = 0;
if (names.length > 0) {
for (i = 0; i < root.length; i++) {
if (root[i].name == names[0]) {
//already in tree
break;
}
}
if (i == root.length) {
let x: Node = { name: names[0] };
root.push(x);
}
root[i].children = this.addToTree(root[i].children, names.slice(1));
}
return root;
}
The result is supposed to look like this:
const TREE_DATA: Node[] = [
{
name: "PathA",
children: [
{ name: "PathA_0" },
{ name: "PathA_1" },
{
name: "PathA_2",
children: [{ name: "a" }, { name: "b" }, { name: "c" }]
}
]
},
{
name: "PathB",
children: [
{ name: "PathB_0" },
{ name: "PathB_1", children: [{ name: "a" }, { name: "b" }] },
{
name: "PathC_2"
}
]
},
{
name: "PathC",
children: [
{ name: "PathB_0" },
{ name: "PathB_1", children: [{ name: "a" }, { name: "b" }] },
{
name: "PathC_2"
}
]
}
];
Here is the Stackblitz Link (https://stackblitz.com/edit/angular-h3btn5?file=src/app/tree-flat-overview-example.ts) to my intents.. Im trying for days now without success.. Thank you so much!!
In plain Javascript, you could reduce the array by using a recursive function for thesearching and assign then new child to a given node.
const
getTree = (node, names) => {
const name = names.shift();
let child = (node.children ??= []).find(q => q.name === name);
if (!child) node.children.push(child = { name });
if (names.length) getTree(child, names);
return node;
},
pathStrings = ["PathA/PathA_0", "PathA/PathA_1", "PathA/PathA_2/a", "PathA/PathA_2/b", "PathA/PathA_2/c"],
tree = pathStrings
.reduce((target, path) => getTree(target, path.split('/')), { children: [] })
.children;
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Nesting then grouping objects into arrays

I'm attempting to convert an array that I get in this format:
data = [
{ name: 'Buttons/Large/Primary', id: '1:23' },
{ name: 'Buttons/Large/Secondary', id: '1:24' },
{ name: 'Buttons/Medium/Primary', id: '1:25' },
{ name: 'Buttons/Medium/Secondary', id: '1:26' },
{ name: 'Forms/Text', id: '2:1' },
{ name: 'Forms/Checkbox', id: '2:2' },
];
to an array in this format:
data = [
{
name: "Buttons",
id: '1:23',
components: [{
name: "Large",
id: '1:23',
components: [{
name: "Primary",
id: '1:23'
}, {
name: "Secondary",
id: '1:24'
}]
},{
name: "Medium",
id: '1:25',
components: [{
name: "Primary",
id: '1:25'
}, {
name: "Secondary",
id: '1:26'
}]
}]
}, {
name: "Forms",
id: '2:1',
components: [{
name: "Text",
id: '2:1'
},{
name: "Checkbox",
id: '2:2'
}]
}
];
My approach was to create arrays from each object in the original dataset by splitting the name property at '/', then nest them inside each other. This is what I have so far, which nests each item in the original array, but lacks grouping them together like my target format shows. Suggestions?
function nestItems(obj, path, value) {
let component = {};
let temp = component;
for (let i = 0; i < path.length; i++) {
let component = temp;
component.name = path[i];
component.id = value;
if (path.length - 1 === i) {
} else {
component.components = {};
temp = component.components;
}
}
obj.push(component)
}
let obj = [];
for (let i = 0; i < data.length; i++) {
let path = data[i].name.split('/');
nestItems(obj, path, data[i].id);
}
console.log(obj)
I agree with your approach for splitting with /.
Here's my approach for using reduce to create a map and generating the final array:
const data = [
{ name: 'Buttons/Large/Primary', id: '1:23' },
{ name: 'Buttons/Large/Secondary', id: '1:24' },
{ name: 'Buttons/Medium/Primary', id: '1:25' },
{ name: 'Buttons/Medium/Secondary', id: '1:26' },
{ name: 'Forms/Text', id: '2:1' },
{ name: 'Forms/Checkbox', id: '2:2' },
];
const map = data.reduce((acc, curr) => {
const { id } = curr;
const [parent, sub, subSub] = curr.name.split('/');
if (acc[parent]) {
if (acc[parent][sub]) {
acc[parent][sub][subSub] = { id };
} else {
acc[parent][sub] = { id };
if (subSub) {
acc[parent][sub][subSub] = { id };
}
}
} else {
acc[parent] = { id };
if (sub && subSub) {
acc[parent][sub] = {
id,
[subSub]: { id }
};
} else if (sub) {
acc[parent][sub] = { id };
};
}
return acc;
}, {});
const result = Object.keys(map).map(parentName => {
const { id: parentId, ...subs } = map[parentName];
const parentObj = { name: parentName, id: parentId };
parentObj.components = Object.keys(subs).map(subName => {
const { id: subId, ...subSubs } = subs[subName];
const subObj = { name: subName, id: subId };
if (Object.keys(subSubs).length) {
subObj.components = Object.keys(subSubs).map(subSubName => ({ name: subSubName, id: subSubs[subSubName].id }));
}
return subObj;
});
return parentObj;
});
console.log(result);

Updating selection of array's values

I have two arrays. Each array could have a different number of objects but they each have the same properties but could have different values. For example
var Array1 = [ { id: '1', value: a },
{ id: '2', value: b } ]
var Array2 = [ { id: '', value: c },
{ id: '', value: d },
{ id: '', value: a } ]
What I want
AfterArray = [ { id: '1', value: a },
{ id: '3', value: c },
{ id: '4', value: d } ]
What's happening is that array1's object will be removed if it doesn't have array2's value. If it does have array2's value, it will keep the original id. If an object is in array2 that isn't in array1, an id will be generated (UUID).
I'm assuming it might go something like this
afterArray = []
this.Array1.forEach((res, i) => {
this.Array2.forEach((res2, 2) => {
if(res.value == res2.value){
afterArray = afterArray.concat(this.Array1[i])
}
else {
// do something if values are not present then add to array.
// if added, add id to those empty properties.
}
})
})
Thanks!
You just need a simple mapping over Array2 with a find inside it, to find the matching value in Array1 if it exists:
const array1 = [
{
id: '1',
value: 'a'
},
{
id: '2',
value: 'b'
}
];
const array2 = [
{
id: '',
value: 'c'
},
{
id: '',
value: 'd'
},
{
id: '',
value: 'a'
}
];
const generateId = (() => {
// example generator function, use your own instead
let possibleIds = ['3', '4'];
let i = -1;
return () => {
i++;
return possibleIds[i];
};
})();
const result = array2.map(({ id, value }) => {
// find a matching value in array1 to merge the id:
const foundArr1Item = array1.find(({ value: ar1Val }) => ar1Val === value);
// otherwise, generate a new ID:
if (foundArr1Item) return { value, id: foundArr1Item.id };
return { value, id: generateId() };
});
console.log(result);
If I understood it right, this should do your job:
(find the comments in the code)
Array1 = [
{
id: '1',
value: "a"
},
{
id: '2',
value: "b"
}
]
Array2 = [
{
id: '',
value: "c"
},
{
id: '',
value: "d"
},
{
id: '',
value: "a"
}
]
// keep Array1's objects if it has a value matching a value from any Array2 object
// Also remove those objects from Array2
newArray1 = Array1.reduce((acc, elem) => {
let indexOfObInArray2 = Array2.findIndex(eachArray2Elem => {
return elem.value == eachArray2Elem.value
});
if (indexOfObInArray2 > -1) {
acc.push(elem);
Array2.splice(indexOfObInArray2, 1);
}
return acc;
}, [])
// Array of ids already taken by Objects from Array2, if they are non empty
idsTakenInArray2 = Array2.reduce((acc, x) => {
if (x.id != "") {
acc.push(x.id);
}
return acc;
}, []);
// random number to give ids
randomId = 1;
Array2 = Array2.map(eachElem => {
if (eachElem.id == '') {
while (Array1.find(eachArray1Elem => {
return eachArray1Elem.id == randomId
}) || idsTakenInArray2.indexOf(randomId) !== -1) {
randomId++;
}
eachElem.id = randomId;
idsTakenInArray2.push(randomId);;
}
return eachElem;
})
console.log(newArray1.concat(Array2));
check this, here is the code online https://stackblitz.com/edit/angular-zhzuqk , check your console you will see what you want to have as result
formtarrays(array1,array2) {
let ar = array1.concat(array2);
// delete items that exist in array1 but not in array2
ar = ar.filter((elem) => {
return !(array1.findIndex(item => item.value === elem.value) !== -1 && array2.findIndex(item => item.value === elem.value) === -1)
})
// get distinct values
const idList = [];
const distinct = [];
ar.forEach((item, index) => {
if (item !== undefined) {
idList['id'] = item.value;
if (idList.indexOf(item.value) < 0) {
if(item.id === '') {
item.id = (index + array1.length).toString();
}
distinct.push(item);
idList.push(item.value);
}
}
})
console.log(distinct);
return distinct;
}

Categories

Resources