Iterate though nested object and get obj by value using es6+ - javascript

For an object of this structure:
const myObj = {
id: 1,
name: '1',
children: [
{
id: 2,
name: '2',
children: [
{
id: 3,
name: '3',
children: []
}
]
},
{
id: 4,
name: '4',
children: [
{
id: 5,
name: '5',
children: [
{
id: 6,
name: '6',
children: [
{
id: 7,
name: '7',
children: []
}
]
}
]
}
]
},
]
}
How would I get an object by value (id)? So I'm looking for a function where if I call this:
findObj(myObj, 6);
It would return this:
{
id: 6,
name: '6',
children: [
{
id: 7,
name: '7',
children: []
}
]
}
Another example:
findObj(myObj, 3);
Would return this:
{
id: 3,
name: '3',
children: []
}
I know I need a recursion function. Heres what I have so far:
findObj(obj, id) {
if (obj.id === id) {
return obj;
}
obj.forEach(x => {
if (x.id === id) {
return x;
} else {
this.findObj(x.children, id);
}
});
}
(This is in an angular class)

At first sight I see some problems.
If you already have the ob.id === id inside the function, you don't need to double check for it later. Also, if findObj is a function defined in the scope like any other variable and is not in the "class" definition, you don't need to call it through this, the same way the first call to the findObj you do it without the this keyword.
Also, at first you pass an object to your method, but then you pass an object array (the obj.children), and also you check for the id inside a forEach method, where you pass a function (the x => { ... thingy), so when you return, you return from that function instead of the main function.
I've fixed it for you. I recommend you to understand what is happening and learn from it, as this will not happen if you know what you are doing (debug is your master here).
findObj(obj, id) {
if (obj.id === id) {
return obj;
}
for (var i = 0; i < obj.children.length; i++) { // No forEach, so when we return, we actually return from findObj
var r = findObj(obj.children[i], id); // We see if we have any return and let the "check logic" be defined on one location instead of duplicated.
if (r) {
return r;
}
}
}

You could check the object or check the children.
function find(object, id) {
var result;
if (object.id === id) return object;
object.children.some(o => result = find(o, id));
return result;
}
const object = { id: 1, name: '1', children: [{ id: 2, name: '2', children: [{ id: 3, name: '3', children: [] }] }, { id: 4, name: '4', children: [{ id: 5, name: '5', children: [{ id: 6, name: '6', children: [{ id: 7, name: '7', children: [] }] }] }] }] };
console.log(find(object, 5));
console.log(find(object, 6));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Two issues:
your return x; statement only returns from the forEach callback, not from the findObj function. Don't use forEach! A simple for … of loop will do
if your recursive call find the object and returns it, you ignore it and just continue iterating. Check whether you got a result and handle it appropriately.
I guess you're not looking for a full solution, so I leave that as an exercise :-)

Related

How to remove data with same id in an array of object using javascript?

in an arry of objects i want to remove object which have same id (duplicated data) using javascript.
below is the input array
const input = [
{
id: '1',
name: 'first',
},
{
id: '1',
name: 'first',
},
{
id: '2',
name: 'second',
},
{
id: '2',
name: 'second',
},
]
so as you see from above array there are duplicating data with id '1' and '2'.
if there is similar id i want include only one
so the expected output is like below,
const output = [
{
id: '1',
name: 'first',
},
{
id: '2',
name: 'second',
},
]
how can i do this. could someone help me with this. i am new to programming thanks.
You can use reduce to filter data from the array based on some condition like bellow
const input = [
{
id: '1',
name: 'first',
},
{
id: '1',
name: 'first',
},
{
id: '2',
name: 'second',
},
{
id: '2',
name: 'second',
},
]
const result = input.reduce((accumulator, current) => {
let exists = accumulator.find(item => {
return item.id === current.id;
});
if(!exists) {
accumulator = accumulator.concat(current);
}
return accumulator;
}, []);
console.log(result);
Similar to this answer. You will have to change the const to let while declaring input though, or use a new variable I suppose.
filtered_input = input.filter((value, index, self) =>
index === self.findIndex((t) => (
t.id === value.id
))
)
There is a lot of good approachs here.
Here is my approach for removing matching property from the original array and sending it back in the return if found.
I prefer to use this one, if you are looping through a external array and matching them, this way you dont need to loop through the whole array again and again for each, because while you are finding the matches it keeps removing them from the original array, increasing performance.
Note that this will return the first match found
let id = "601985b485d9281d64056953"
let contacts = [{
...,
parent: "601985b485d9281d64056953",
...,
},
{
...,
parent: "601985b485d9281d64065128",
...,
}
]
function findAndRemoveObjectFromArray(array, internalProperty, externalProperty, convertType = "string", returnObject = false) {
let objIndex = -1
if (convertType === "string") objIndex = array.findIndex((obj) => String(obj[`${internalProperty}`]) === String(externalProperty));
if (convertType === "number") objIndex = array.findIndex((obj) => Number(obj[`${internalProperty}`]) === Number(externalProperty));
if (objIndex > -1) {
const object = array.splice(objIndex, 1);
if (returnObject) return object.shift()
return object
}
return [];
}
let currentContact = findAndRemoveObjectFromArray(contacts, "parent", id, 'string', true)
// Results:{..., parent: "601985b485d9281d64056953",...}
you could use Set to get rid of the duplicate data like this
const input = [
{
id: '1',
name: 'first',
},
{
id: '1',
name: 'first',
},
{
id: '2',
name: 'second',
},
{
id: '2',
name: 'second',
},
]
const result = [...new Set(input.map(JSON.stringify))].map(JSON.parse)
console.log(result)
Below is another approach
const input = [
{
id: '1',
name: 'first',
},
{
id: '1',
name: 'first',
},
{
id: '2',
name: 'second',
},
{
id: '2',
name: 'second',
},
];
const uniqueIds = new Set();
const uniqueList = input.filter(element => {
const isDuplicate = uniqueIds.has(element.id);
uniqueIds.add(element.id);
return !isDuplicate;
});
console.log(uniqueList);

Need to return objects containing the key which referred from array of objects

I am trying to return the objects which has key of submittedDate. But if i am trying with find() It's returning only the first object. And for map it's returning undefined for object not having submittedDate key. Please find my code with data and also the result I want. thanks in advance.
const data = [
{
id: '1',
name: 'Tully Stark',
submittedData:'mmmmm'
},
{
id:'2',
name: 'Nalani Romanova',
},
{
id:'3',
name: 'Nalani Romanova',
submittedData:'mmmmm'
}
]
const submitDate = data.find(item => item.submittedData)
console.log(submitDate)
data to return
const returnData = [
{
id: '1',
name: 'Tully Stark',
submittedData:'mmmmm'
},
{
id:'3',
name: 'Nalani Romanova',
submittedData:'mmmmm'
}
]
the .find by definition only returns the first matching object.
Array.prototype.find()
The find() method returns the value of the first element in the provided array that satisfies the provided testing function. If no values satisfy the testing function, undefined is returned.
You need to use .filter
const submitDate = data.filter(item => item.submittedData)
const data = [{
id: '1',
name: 'Tully Stark',
submittedData: 'mmmmm'
},
{
id: '2',
name: 'Nalani Romanova',
},
{
id: '3',
name: 'Nalani Romanova',
submittedData: 'mmmmm'
}
]
const submitDate = data.filter(item => item.submittedData)
console.log(submitDate)
You can use Array.filter(), this will return all matching items.
const data = [ { id: '1', name: 'Tully Stark', submittedData:'mmmmm' }, { id:'2', name: 'Nalani Romanova', }, { id:'3', name: 'Nalani Romanova', submittedData:'mmmmm' } ]
const submitDate = data.filter(item => item.submittedData)
console.log(submitDate)
.as-console-wrapper { max-height: 100% !important; top: 0; }

How to create a function to retrieve all children's id from a nested object

I want to retrieve all child ids of a specific group, which can be deeply nested or not.
Here is a sample json:
[
{
id: 1,
name: 'Desjardins Group 1',
children: [
{ id: 2, name: 'Analysts', children: [] },
{ id: 3, name: 'Administration', children: [] }
]
},
{
id: 4,
name: 'Desjardins Group 2',
children: [
{ id: 5, name: 'Consultants1', children: [] },
{
id: 6,
name: 'Consultant2',
children: [
{
id: 7, name: 'Interns', children: [
{ id: 8, name: 'subInterns1', children: [] },
{ id: 9, name: 'subInterns2', children: [] },
{ id: 10, name: 'subInterns3', children: [] }
]
}
]
}
]
}
]
I'm trying to make a function that takes an id has a parameter, and return all child ids.
Ex: getChildGroups(6) would return 7, 8, 9 and 10.
I guess recursive function and filters are the way to go, but i can't find a proper example.
Here's a simplified version of Johann Bauer's answer.
The first function just finds the first node that matches the given ID, with no need for any accumulation of data:
function findNode(data, id) {
if (!Array.isArray(data)) return;
for (let entry of data) {
if (entry.id === id) {
return entry;
} else {
const node = findNode(entry.children, id);
if (node) {
return node;
}
}
}
}
This second function just gets the child IDs, storing them in the passed array, without any intermediate arrays being created:
function getChildIds(node, result = []) {
if (!node) return;
if (!Array.isArray(node.children)) return;
for (let entry of node.children) {
result.push(entry.id);
getChildIds(entry, result);
}
return result;
}
It might be a good idea to split your problem into two smaller problems:
Find a group of ID x somewhere nested in the graph
Given a node, return all their sub-node IDs recursively
The solution to the first problem could look something like this:
function findGroupId(o, id) {
if (o.id == id) {
// We found it!
return o;
}
if (Array.isArray(o)) {
// If we start with a list of objects, pretend it is the root node
o = {children: o}
}
let results = [];
for (let c of o.children) {
// recursively call this function again
results.push(findGroupId(c, id))
}
// return the first matching node
return results.filter(r => r !== undefined)[0];
}
And for the second problem:
function getAllChildrenIDs(o) {
if (o.children === undefined)
return [];
let ids = [];
for (c of o.children) {
ids.push(c.id);
// recursively call this function again
for (id of getAllChildrenIDs(c))
ids.push(id);
}
return ids;
}
And if we put this together:
let example = [{
id: 1,
name: 'Desjardins Group 1',
children: [{
id: 2,
name: 'Analysts',
children: []
},
{
id: 3,
name: 'Administration',
children: []
}
]
},
{
id: 4,
name: 'Desjardins Group 2',
children: [{
id: 5,
name: 'Consultants1',
children: []
},
{
id: 6,
name: 'Consultant2',
children: [{
id: 7,
name: 'Interns',
children: [{
id: 8,
name: 'subInterns1',
children: []
},
{
id: 9,
name: 'subInterns2',
children: []
},
{
id: 10,
name: 'subInterns3',
children: []
}
]
}]
}
]
}
];
function findGroupId(o, id) {
if (o.id == id) {
return o;
}
if (Array.isArray(o)) {
o = {
children: o
}
}
let results = [];
for (let c of o.children) {
results.push(findGroupId(c, id))
}
return results.filter(r => r !== undefined)[0];
}
function getAllChildrenIDs(o) {
if (o.children === undefined)
return [];
let ids = [];
for (c of o.children) {
ids.push(c.id);
for (id of getAllChildrenIDs(c))
ids.push(id);
}
return ids;
}
console.log(getAllChildrenIDs(findGroupId(example, 6)))

Remove equal Key between two JSON

I have a big problem. I want to create a function that remove equal 'parts' between two JSON files, with the output of function having the same structure, but without the 'equal parts'.
An example i have a JSON version of a DOM tree and i want keep only the difference between pages (remove nav footer ...)
Example
const a = {
id: '1',
child: [
{
id: '2',
child: [
{
id: '1'
},
{
id: '2'
}
]
},
{
id: '3',
child: [
{
id: '1'
},
{
id: '5'
}
]
}
]
}
And
const b = {
id: '1',
child: [
{
id: '2',
child: [
{
id: '1'
},
{
id: '4'
}
]
},
{
id: '3',
child: [
{
id: '1'
},
{
id: '4'
}
]
}
]
}
With a function
diff(a, b)
This result
{
id: '1',
child: [
{
id: '2',
child: [
{
id: '2'
}
]
},
{
id: '3',
child: [
{
id: '5'
}
]
}
]
}
I created this based on recursive function
const diff = (a, b) => {
if (Array.isArray(a)) {
}
if (typeof a === 'object') {
// ...
extract(a.child, b.child);
}
}
How do I do this? Is there an npm package? or with JSON Path? I want to create a function that remove the equal 'parts' between two JSON files with the output of the function having the same structure, but without the 'equal parts'.
I assume you can't guarantee the order of the id/value pairs?
I would suggest that you first recursively merge each layer, then go through and remove duplicates.
Edited With Code from the Recursion function
let c = [] // merged structure
for (var i=0; i<a.length; i++)
{
for (let j=0; j<b.length; j++)
{
if (a[i].id === j[i].id) {
c[i] = { id: a[i].id, child: a[i].child.concat(b[i].child) }
// next, sort the c[i].child, and
// loop through c[i].child and recur over any cases where
// c[i].child[k].id === c[i].child[k+1].id
// at the end, go back up the chain removing any structs where the
// id is a duplicate and has no children left
}
}
}

filter objects from array that have the same value at given key

Let's say we have an array that looks like this:
[
{
id: 0,
name: 'A'
},
{
id: 1,
name:'A'
},
{
id: 2,
name: 'C'
},
{
id: 3,
name: 'B'
},
{
id: 4,
name: 'B'
}
]
I want to keep only this objects that have the same value at 'name' key. So the output looks like this:
[
{
id: 0,
name: 'A'
},
{
id: 1,
name:'A'
},
{
id: 3,
name: 'B'
},
{
id: 4,
name: 'B'
}
]
I wanted to use lodash but I don't see any method for this case.
You can try something like this:
Idea:
Loop over the data and create a list of names with their count.
Loop over data again and filter out any object that has count < 2
var data = [{ id: 0, name: 'A' }, { id: 1, name: 'A' }, { id: 2, name: 'C' }, { id: 3, name: 'B' }, { id: 4, name: 'B' }];
var countList = data.reduce(function(p, c){
p[c.name] = (p[c.name] || 0) + 1;
return p;
}, {});
var result = data.filter(function(obj){
return countList[obj.name] > 1;
});
console.log(result)
A lodash approach that may (or may not) be easier to follow the steps of:
const originalArray = [{ id: 0, name: 'A' }, { id: 1, name: 'A' }, { id: 2, name: 'C' }, { id: 3, name: 'B' }, { id: 4, name: 'B' }];
const newArray =
_(originalArray)
.groupBy('name') // when names are the same => same group. this gets us an array of groups (arrays)
.filter(group => group.length == 2) // keep only the groups with two items in them
.flatten() // flatten array of arrays down to just one array
.value();
console.log(newArray)
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.11/lodash.min.js"></script>
A shorter solution with array.filter and array.some:
var data = [ { ... }, ... ]; // Your array
var newData = data.filter((elt, eltIndex) => data.some((sameNameElt, sameNameEltIndex) => sameNameElt.name === elt.name && sameNameEltIndex !== eltIndex));
console.log("new table: ", newTable);
You could use a hash table and a single loop for mapping the objects or just an empty array, then concat the result with an empty array.
var data = [{ id: 0, name: 'A' }, { id: 1, name: 'A' }, { id: 2, name: 'C' }, { id: 3, name: 'B' }, { id: 4, name: 'B' }],
hash = Object.create(null),
result = Array.prototype.concat.apply([], data.map(function (o, i) {
if (hash[o.name]) {
hash[o.name].update && hash[o.name].temp.push(hash[o.name].object);
hash[o.name].update = false;
return o;
}
hash[o.name] = { object: o, temp: [], update: true };
return hash[o.name].temp;
}));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Categories