Find all occurance of keys in the object and change the value - javascript

I want to find all occurances of keys and change its value. I want the id and name to be changed in the entire object to a new value.
const myObj = {
id: 1,
name: "a",
children: [
{
id: 2,
name: "b",
children: [
{
id: 3,
name: "c",
}
]
},
{
id: 4,
name: "d",
children: [
{
id: 5,
name: "e",
children: [
{
id: 6,
name: "f",
children: [
{
id: 7,
name: "g",
}
]
}
]
}
]
},
]
}
Code which i tried, not sure how can i get two properties and change their values.
function findAllByKey(obj, id, name) {
let constObj = Object.entries(obj);
console.log(obj)
Object.entries(constObj)
.reduce(([key, value]) => (key === id)
? obj[key] = "123"
: (typeof value === 'object'))
return obj;
}
// USAGE,
console.log(findAllByKey(myObj, 'id', 'name'))

You will need to utilize recursion for this.
Dave Newton gives a good argument in saying that the children properties are the ones containing most of the properties you want to change, so I would recommend you loop through each of the children properties and change their ID in that way. However, the answer I give you will work in more generic instances (for those who may need a different approach).
function findAllByKeyAndReplace(object,key,replace){
const looper = function(obj){
for (let k in obj){
if(k === key){
obj[k] = replace;
}
else if("object" === typeof obj[k]){
looper(obj[k]);
}
}
}
looper(object);
}
const myObj = {
id: 1,
name: "a",
children: [
{
id: 2,
name: "b",
children: [
{
id: 3,
name: "c",
}
]
},
{
id: 4,
name: "d",
children: [
{
id: 5,
name: "e",
children: [
{
id: 6,
name: "f",
children: [
{
id: 7,
name: "g",
}
]
}
]
}
]
},
]
}
findAllByKeyAndReplace(myObj,"id","123");
findAllByKeyAndReplace(myObj,"name","pickles");
console.log(myObj);
Note that I am pretty old school about this so please tell me if there is any way to improve on this, as it does take up a bit of power.
Oh wait, I see you want to return a list of the objects and not bulk-replace them with one value. Ok, that is quite easily done, too.
Instead of changing them just add them to an array which will be returned on the looper being finished:
function findAllByKey(object, key) {
const collection = [];
const looper = function(obj) {
for (let k in obj) {
if (k === key) {
collection.push(obj[k]);
} else if ("object" === typeof obj[k]) {
looper(obj[k]);
}
}
}
looper(object);
return collection;
}
const myObj = {
id: 1,
name: "a",
children: [{
id: 2,
name: "b",
children: [{
id: 3,
name: "c",
}]
},
{
id: 4,
name: "d",
children: [{
id: 5,
name: "e",
children: [{
id: 6,
name: "f",
children: [{
id: 7,
name: "g",
}]
}]
}]
},
]
}
console.log(findAllByKey(myObj, "id"));
console.log(findAllByKey(myObj, "name"));

Related

how convert my nested object to array in javascript

I have an object with nested objects. In this object, each objects having two or more sub-objects. I want to get together all sub-objects into an array of data. How to do with JavaScript?
const category = {
id: 1,
title: "a",
level: 2,
__parent: {
id: 2,
title: "b",
level: 1,
__parent: {
id: 3,
title: "c",
level: 0,
}
}
};
The output I want is this:
[{
id: 1,
title: "a",
level: 2,
__parent: null
},
{
id: 2,
title: "b",
level: 1,
__parent: null
},
{
id: 3,
title: "c",
level: 0,
__parent:null
}]
Case 1: Using Recursion
You can make recursive function like this:
const category = {
id: 1,
title: "a",
level: 2,
__parent: {
id: 2,
title: "b",
level: 1,
__parent: {
id: 3,
title: "c",
level: 0,
}
}
};
const result = [];
function recursiveOuput(data){
let tempObj = {};
Object.keys(data).map((key, index) => {
if(typeof data[key] !== 'object'){
tempObj[key] = data[key];
if(!Object.keys(data).includes('__parent') && (index === Object.keys(data).length -1)){
tempObj['__parent'] = null;
result.push(tempObj);
}
}else{
tempObj['__parent'] = null;
result.push(tempObj);
recursiveOuput(data[key]);
}
})
return result;
};
console.log(recursiveOuput(category));
Case 2: Using While loop
const category = {
id: 1,
title: "a",
level: 2,
__parent: {
id: 2,
title: "b",
level: 1,
__parent: {
id: 3,
title: "c",
level: 0,
}
}
};
const result = [];
let parent = category;
while (parent) {
result.push({
id: parent.id,
level: parent.level,
title: parent.title,
__parent: null
})
parent = parent.__parent
};
console.log(result);
It Be some like this :
const myArray = []
const category = ...;
function foo(obj){
myArray.push({
title:obj.title,
....
})
if (obj._parent)
foo(obj._parent)
}
foo(category)
You essentially want to get the list of ancestors.
const ancestors = []
var parent = category
while (parent) {
ancestors.push({
id: parent.id,
level: parent.level,
__parent: null
})
parent = parent.__parent
}
use recursion to extract objects:
const category = {
id: 1,
title: "a",
level: 2,
__parent: {
id: 2,
title: "b",
level: 1,
__parent: {
id: 3,
title: "c",
level: 0,
}
}
};
function extract(obj,arr=[]){
arr.push({...obj,__parent:null})
if(!obj.__parent){
return arr
}
return extract(obj.__parent,arr)
}
let result = extract(category)
console.log(result)
Using Spread/Rest Operator
const category = {
id: 1,
title: "a",
level: 2,
__parent: {
id: 2,
title: "b",
level: 1,
__parent: {
id: 3,
title: "c",
level: 0,
}
}
};
const {__parent, ...firstObject} = category;
result = [
firstObject,
{...category['__parent'], '__parent': null },
{...category['__parent']['__parent'], '__parent': null},
];
console.log(result);

how to group objects in array that are pointing at eact other

I have an array of objects, each object is pointing at another object in the "related" field.
I need to group the object with the related field so I can output them in groups on my website.
how can I group something like this
[
{
id: 1,
name: "dog",
related: "cat",
},
{
id: 2,
name: "cat",
related: "dog",
},
{
id: 3,
name: "shark",
related: "whale",
},
];
to this ?
[
[
{
id: 1,
name: "dog",
related: "cat",
},
{
id: 2,
name: "cat",
related: "dog",
},
],
[
{
id: 3,
name: "shark",
related: "whale",
},
],
];
Here i've tried this. It'll only work for two way binding relations.
const data = [
{
id: 1,
name: "dog",
related: "cat",
},
{
id: 2,
name: "cat",
related: "dog",
},
{
id: 3,
name: "shark",
related: "whale",
},
{
id: 4,
name: "whale",
related: "shark",
},
{
id: 5,
name: "test",
related: "none",
},
];
const categoriesData = (data) => {
const dataObj = data.reduce((obj, item) => {
if (!obj[item.name]) obj[item.related] = [item];
else if (obj[item.name] && obj[item.name][0].name === item.related)
obj[item.name].push(item);
return obj;
}, {});
return Object.values(dataObj);
};
console.log(categoriesData(data));
You can reduce it:
var data=[ { id: 1, name: "dog", related: "cat", }, { id: 2, name: "cat", related: "dog", }, { id: 3, name: "shark", related: "whale", }];
var result = Object.values(data.reduce((acc, elem)=>{
const key = [elem.name, elem.related].sort((a,b)=>a.localeCompare(b)).join('|');
acc[key] = [...(acc[key] || []), elem];
return acc;
},{}));
console.log(result);
this doesn't exactly answer your question, but is more of an "outside the box" solution. your data would be much simpler, straight forward and easy to manage if you simply add a "category" field.
[
{
id: 1,
name: "dog",
related: "cat",
category: 1,
},
{
id: 2,
name: "cat",
related: "dog",
category: 1,
},
{
id: 3,
name: "shark",
related: "whale",
category: 2,
},
{
id: 3,
name: "whale",
related: "shark",
category: 2,
},
];
One way of doing this would be to create a Map holding the animals. Then for each animal in this collection (that is not removed) create a group and remove it from the collection. Find related animals and add them to the same group, also removing them from the collection.
const animals = [
{ id: 1, name: "dog", related: "cat" },
{ id: 2, name: "cat", related: "dog" },
{ id: 3, name: "shark", related: "whale" },
];
const animalsMap = new Map(animals.map(animal => [animal.name, animal]));
const animalGroups = [];
for (let [,animal] of animalsMap) {
const group = Array.of(animal);
while (
animalsMap.delete(animal.name),
animal = animalsMap.get(animal.related)
) {
group.push(animal);
}
animalGroups.push(group);
}
console.log(animalGroups);
You could gather all relations first, then build groups of related values and then map the groups by filtering the data.
This approach works with longer chains of related objects.
function getGroups(k, group) {
if (seen.has(k)) return;
seen.add(k);
group.push(k);
relations[k].forEach(v => seen.has(v) || getGroups(v, group));
}
var data = [{ id: 1, name: "dog", related: "cat" }, { id: 2, name: "cat", related: "dog" }, { id: 3, name: "shark", related: "whale" }],
relations = {},
groups = [],
seen = new Set,
result;
data.forEach(({ name, related }) => {
if (!relations[name]) relations[name] = [];
relations[name].push(related);
if (!relations[related]) relations[related] = [];
relations[related].push(name);
});
Object.keys(relations).forEach(k => {
let group = [];
getGroups(k, group);
if (group.length) groups.push(group);
});
result = groups.map(group => data.filter(({ name }) => group.includes(name)));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

How to mark parent tree node as selected if all the child nodes are selected

I have a json that forms a tree.
[{
id: 1,
name: "A",
isSelected: false,
child: [
{
id: 2,
name: "B",
isSelected: false,
}
{
id: 3,
name: "C",
isSelected: false,
}
]
},
{
id: 4,
name: "D",
isSelected: false,
child: [
{
id: 5,
name: "E",
isSelected: false,
}
{
id: 6,
name: "F",
isSelected: false,
}
]
}]
Now I have an array of selected nodes
[{id: 2},{id:3}]
And markSelectedNodes method to mark the nodes as selected based on selected node array
markSelectedNodes(_nodes) {
_nodes.forEach((_node) => {
// match if node exists in selected nodes
const _isFound = this.selectedNodes.find((_selectedNode) => _selectedNode.id === _node.id);
if (_isFound) {
_node.isSelected = true;
} else {
_node.isSelected = false;
}
// removed the node from selecedNode list as this has been marked selected
this.selectedNodes = this.selectedNodes.filter((_selectedNode) => _selectedNode !== _node.id);
// go through the child
if (_node.child && _node.child.length) {
this.markSelectedNodes(_node.child);
}
});
}
Now if all the child has been marked selected it should also mark the parent node as selected.
Can this be implemented through stack?
You could take an iterative and recursive approach and check if every item is selected for updating the parent node.
function update(array = [], ids) {
return !!array.length
&& array.every(node => node.isSelected = ids.includes(node.id) || update(node.child, ids));
}
var data = [{ id: 1, name: "A", isSelected: false, child: [{ id: 2, name: "B", isSelected: false }, { id: 3, name: "C", isSelected: false }] }, { id: 4, name: "D", isSelected: false, child: [{ id: 5, name: "E", isSelected: false }, { id: 6, name: "F", isSelected: false }] }],
nodes = [{ id: 2 }, { id: 3 }];
update(data, nodes.map(({ id }) => id));
console.log(data);
.as-console-wrapper { max-height: 100% !important; top: 0; }
If you need to iterate all nested arrays, you could store the flag, iterate and return the result of the iteration.
function update(array = [], ids) {
if (!array.length) return false;
var selected = true;
array.forEach(node => {
node.isSelected = ids.includes(node.id) || update(node.child, ids);
selected = selected && node.isSelected;
});
return selected;
}
var data = [{ id: 1, name: "A", isSelected: false, child: [{ id: 2, name: "B", isSelected: false }, { id: 3, name: "C", isSelected: false }] }, { id: 4, name: "D", isSelected: true, child: [{ id: 5, name: "E", isSelected: true }, { id: 6, name: "F", isSelected: true }] }],
nodes = [{ id: 2 }, { id: 3 }];
update(data, nodes.map(({ id }) => id));
console.log(data);
.as-console-wrapper { max-height: 100% !important; top: 0; }

filter nested tree object without losing structure

I have nested tree object I would like filter through without losing structure
var items = [
{
name: "a1",
id: 1,
children: [{
name: "a2",
id: 2,
children: [{
name: "a3",
id: 3
}]
}]
}
];
so for example if id == 2 remove object with id 2 and his children
if id == 3 only remove object with id 3
this's just apiece of object to make question clean but the object it self contains more and more :)
using vanilla javascript, _lodash or Angular2 it's okay
thank you
You can create recursive function using filter() and also continue filtering children if value is Array.
var items = [{
name: "a1",
id: 1,
children: [{
name: "a2",
id: 2,
children: [{
name: "a3",
id: 3
}, ]
}]
}];
function filterData(data, id) {
var r = data.filter(function(o) {
Object.keys(o).forEach(function(e) {
if (Array.isArray(o[e])) o[e] = filterData(o[e], id);
})
return o.id != id
})
return r;
}
console.log(filterData(items, 3))
console.log(filterData(items, 2))
Update: As Nina said if you know that children is property with array you don't need to loop keys you can directly target children property.
var items = [{
name: "a1",
id: 1,
children: [{
name: "a2",
id: 2,
children: [{
name: "a3",
id: 3
}, ]
}]
}];
const filterData = (data, id) => data.filter(o => {
if (o.children) o.children = filterData(o.children, id);
return o.id != id
})
console.log(JSON.stringify(filterData(items, 3), 0, 2))
console.log(JSON.stringify(filterData(items, 2), 0, 2))
If it's ok for your case to use Lodash+Deepdash, then:
let filtered = _.filterDeep(items,(i)=>i.id!=3,{tree:true});
Here is a demo Codepen
You could use an iterative approach with Array#some and call the callback iter recursive for the children. I found, splice.
function deleteItem(id) {
items.some(function iter(a, i, aa) {
if (a.id === id) {
aa.splice(i, 1);
return true;
}
return a.children.some(iter);
});
}
var items = [{ name: "a1", id: 1, children: [{ name: "a2", id: 2, children: [{ name: "a3", id: 3 }] }] }];
console.log(items);
deleteItem(3);
console.log(items);
deleteItem(2);
console.log(items);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Use recursive function:
var items = [
{
name: "a1",
id: 1,
children: [{
name: "a2",
id: 2,
children: [{
name: "a3",
id: 3,
children: [{
name: "a4",
id: 4,
}]
}]
}]
}
];
function filterId(items, id) {
var len = items.length;
while (len--) {
if (items[len].id === id) {
items.splice(len, 1);
break;
} else {
filterId(items[len].children, id);
}
}
return items;
}
// filtering out the item with 'id' = 4
console.log(filterId(items, 4));
// filtering out the item with 'id' = 2
console.log(filterId(items, 2));

how to convert array of object into object?

I have an array of object and i cant convert it into an object. The array of object that i have is as below:
var x = [{
name: "a",
items: [{
name: "b",
items: [{
name: "c"
}]
}]
},
{
name: "d",
items: [{
name: "e",
items: [{
name: "f"
}]
}]
}];
and i want to convert it into object like this:
{
name: "a",
items: [{
name: "b",
items: [{
name: "c"
}]
}]
},{
name: "d",
items: [{
name: "e",
items: [{
name: "f"
}]
}]
}
I used one of the stackoverflow solution
function toObject(arr) {
var rv = {};
for (var i = 0; i < arr.length; ++i)
rv[i] = arr[i];
return rv;
}
But it convert as follows:
{
"0":{
"name":"a",
"items":[{"name":"b","items":[{"name":"c"}]}]},
"1":{
"name":"d",
"items":[{"name":"e","items":[{"name":"f"}]}]}
}
So, how to get the object in the format i gave?
The following is not one object
{
name: "a",
items: [{
name: "b",
items: [{
name: "c"
}]
}]
}, // this comma shows you that it's can't be one object.
{
name: "d",
items: [{
name: "e",
items: [{
name: "f"
}]
}]
}
You can't have an object with the two different properties of the same name.
You want to reduce the array into a single object:
var output = x.reduce(function(output, arrayItem) {
var key = arrayItem.name;
output[key] = arrayItem;
return output;
}, { });
maybe you want something like this
var x = [{
name: "a",
items: [{
name: "b",
items: [{
name: "c"
}]
}]
},
{
name: "d",
items: [{
name: "e",
items: [{
name: "f"
}]
}]
}];
function toObject(arr) {
var rv = {};
for (var i = 0; i < arr.length; ++i)
rv[arr[i].name] = { items: arr[i].items} ;
return rv;
}
console.log(toObject(x));
http://jsfiddle.net/jypahwj4/
One problem that your doing to run into is that when creating an object from that array you will have multiple keys with the same name so each name: will be overridden by the next one you try and add to the object. In your function because you are doing rv[i] and i is a number that is why you are getting the numbers in your newly created object.
A possible solution would be
function toObject(arr) {
var rv = {};
for (var i = 0; i < arr.length; ++i)
rv[arr[i].name] = arr[i].items;
return rv;
}
But this would make the key of the outer part of the object be equal to the name: of the original array of objects

Categories

Resources