Remove equal Key between two JSON - javascript

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
}
}
}

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);

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)))

Search nested array of objects and return the full path to the object

Say I have the following object:
const pages = [
{
name: 'a',
id: '1',
pages: [
{
name: 'b',
id: '1.1',
pages: []
},
{
name: 'c',
id: '1.2',
pages: [
{
name: 'd',
id: '1.2.1',
pages: []
}
]
},
]
},
{
name: 'e',
id: '2',
pages: []
}
]
I'd like to perform a function on this nested object that will return the 'path' to the object I'm searching for.
So something like
getPath(pages, '1.2.1')
will return:
[
{
name: 'a',
id: '1'
},
{
name: 'c',
id: '1.2'
},
{
name: 'd'
id: '1.2.1'
}
]
Here's what I have so far. Its just a recursive function to find the object I want. I'm stuck on how to build the path as I'm traversing through the object though.
const pages = [
{
name: 'a',
id: '1',
pages: [
{
name: 'b',
id: '1.1',
pages: []
},
{
name: 'c',
id: '1.2',
pages: [
{
name: 'd',
id: '1.2.1',
pages: []
}
]
},
]
},
{
name: 'e',
id: '2',
pages: []
}
]
function getPath(pages, pageId) {
let path = [];
for (let i = 0; i < pages.length; i++) {
const item = search(pages[i], pageId);
// build path here?
if (item) {
return item;
}
}
}
function search(obj, id) {
if (obj.id === id) {
return obj;
}
for (let i = 0; i < obj.pages.length; i++) {
const possibleResult = search(obj.pages[i], id);
if (possibleResult) {
return possibleResult;
}
}
}
console.log(getPath(pages, '1.2.1'))
You can use this alternative for getting the path, it's a recursive approach and uses an array called path as param in order to track the visited levels.
Assuming the ID's are uniques regardless of the location/level.
const pages = [ { name: 'a', id: '1', pages: [ { name: 'b', id: '1.1', pages: [] }, { name: 'c', id: '1.2', pages: [ { name: 'd', id: '1.2.1', pages: [] } ] }, ] }, { name: 'e', id: '2', pages: [] }];
const loop = (arr, target, index, path) => {
if (arr[index].id === target) {
path.push({name: arr[index].name, id: arr[index].id});
} else if (arr[index].pages.length) {
path.push({name: arr[index].name, id: arr[index].id});
arr[index].pages.forEach((_, i, a) => {
loop(a, target, i, path);
});
if (path[path.length - 1].id === arr[index].id) path.pop();
}
};
let getPath = (arr, target) => {
let path = [];
arr.forEach((_, i, a) => loop(a, target, i, path));
return path;
};
console.log(getPath(pages, '1.2.1'));
.as-console-wrapper { max-height: 100% !important; top: 0; }

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

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 :-)

Make nested object structure from flat structure and reverse?

I have a flat array. Like this:
const inputArray = [
{
path: '1',
id: '1'
},
{
path: '2',
id: '2'
},
{
path: '3',
id: '3'
},
{
path: '3.4',
id: '4'
},
{
path: '3.5',
id: '5'
},
{
path: '3.4.6',
id: '6'
},
{
path: '3.4.7',
id: '7'
},
{
path: '8',
id: '8'
},
]
Where path is unique path of the element by id. For example, path: '3.5' means that this object is child for object with id: '3'. And path: '3.4.6' is child for path: '3.4'.
I want collect them to nested structure. So result should be like this.
const result = [
{
path: '1',
id: '1',
children: []
},
{
path: '2',
id: '2',
children: []
},
{
path: '3',
id: '3',
children: [
{
path: '3.4',
id: '4',
children: [
{
path: '3.4.6',
id: '6',
children: []
},
{
path: '3.4.7',
id: '7',
children: []
},
]
},
{
path: '3.5',
id: '5',
children: []
},
]
},
{
path: '8',
id: '8',
children: []
},
]
And also i need a second algorithm to convert them back, from nested to flat structure. Can you give ad advice please?
UPDATE: Data is not sorted. Here is my try, but there is too much code and it fails in some cases. I feel that there should be better way to make this.
Use Array.reduce, Array.findIndex, Array.push and Array.shift
Convert to Tree
Assuming that input array is sorted by path, otherwise, you will not sort inputArray.sort((a,b) => a.path - b.path);
Reduce the array to form the tree
Create an array of the hierarchy by splitting the path and creating a number array from it
Create a function addChildren which will take 3 inputs
a -> Parent object (array) in which object will be inserted
c -> object that needs to be inserted
t -> array of hierarchy of the object which needs to be inserted
Function takes the first value of t and if its last in hierarchy then
it means that the a is the valid placeholder for the object. Hence,
push it there. In case there are remaining values then find the
placeholder from the array by matching the id. Now, again call the
function with a will become the children array of the matched object,
c remains the same and t will be the remaining array of hierarchy.
const inputArray = [{path:'1',id:'1'},{path:'2',id:'2'},{path:'3',id:'3'},{path:'3.4',id:'4'},{path:'3.5',id:'5'},{path:'3.4.6',id:'6'},{path:'3.4.7',id:'7'},{path:'8',id:'8'}];
const result = inputArray.reduce((a,c) => {
let t = c.path.split(".").map(Number);
addChildren(a,c,t);
return a;
}, []);
function addChildren(a, c, t) {
let val = t.shift();
if(!t.length) {
a.push({...c, children : []});
} else {
var i = a.findIndex(({id}) => Number(id) === val);
addChildren(a[i].children, c, t);
}
}
console.log(result);
Flatten Tree
Create a function that take 2 inputs
a -> Input array (Array of children)
r -> Resulting array
Function iterates over the input array and pushes the objects in the
result array and check for any children, if yes, then call the function
for children as well
var inputArray = [{path:'1',id:'1',children:[]},{path:'2',id:'2',children:[]},{path:'3',id:'3',children:[{path:'3.4',id:'4',children:[{path:'3.4.6',id:'6',children:[]},{path:'3.4.7',id:'7',children:[]},]},{path:'3.5',id:'5',children:[]},]},{path:'8',id:'8',children:[]},];
function flattenArray(a, r) {
a.forEach(({children, ...rest}) => {
r.push(rest);
if(children) flattenArray(children, r)
});
}
var result = [];
flattenArray(inputArray, result);
console.log(result);
If you want more versability, you can create tree-structure from the input and then do whatever you want with it (i.e. output in some format you want, add methods for finding some item etc.)
const inputArray = [
{
path: '1',
id: '1'
},
{
path: '2',
id: '2'
},
{
path: '3',
id: '3'
},
{
path: '3.4',
id: '4'
},
{
path: '3.5',
id: '5'
},
{
path: '3.4.6',
id: '6'
},
{
path: '3.4.7',
id: '7'
},
{
path: '8',
id: '8'
},
];
class Tree {
constructor() {
this.root = {};
}
addNewNode(path, node) {
const pathArr = path.split('.');
let currentNode = this.root;
pathArr.forEach(item => {
if (!currentNode[item]) {
currentNode[item] = {};
}
currentNode = currentNode[item];
});
currentNode.data = node;
}
}
const tree = new Tree();
inputArray.forEach(val => tree.addNewNode(val.path, val));
console.log(tree);
The output is the output of a tree, not the exact output you included - it is up to you, how you proceed with it.
You could use an object as helper structure for unsorted data and build a tree.
For getting a flat array, you could iterate the tree and concat the flat children with a recursive function.
function getTree(array) {
var o = {};
array.forEach(({ id, path }) => {
var parents = path.split('.'),
parent = parents[parents.length - 2];
Object.assign(o[id] = o[id] || {}, { id, path });
o[parent] = o[parent] || {};
o[parent].children = o[parent].children || [];
o[parent].children.push(o[id]);
});
return o.undefined.children;
}
function getFlat(array = []) {
return array.reduce((r, { id, path, children }) =>
r.concat({ id, path }, getFlat(children)), []);
}
var input = [{ path: '1', id: '1' }, { path: '2', id: '2' }, { path: '3', id: '3' }, { path: '3.4', id: '4' }, { path: '3.5', id: '5' }, { path: '3.4.6', id: '6' }, { path: '3.4.7', id: '7' }, { path: '8', id: '8' }],
tree = getTree(input),
flat = getFlat(tree);
console.log(tree);
console.log(flat);
.as-console-wrapper { max-height: 100% !important; top: 0; }
A solution without keeping path.
function getTree(array) {
var o = {};
array.forEach(({ id, path }) => {
var parents = path.split('.'),
parent = parents[parents.length - 2];
Object.assign(o[id] = o[id] || {}, { id });
o[parent] = o[parent] || {};
o[parent].children = o[parent].children || [];
o[parent].children.push(o[id]);
});
return o.undefined.children;
}
function getFlat(array = [], path = []) {
return array.reduce((r, { id, children }) => {
var p = path.concat(id);
return r.concat({ id, path: p.join('.') }, getFlat(children, p));
}, []);
}
var input = [{ path: '1', id: '1' }, { path: '2', id: '2' }, { path: '3', id: '3' }, { path: '3.4', id: '4' }, { path: '3.5', id: '5' }, { path: '3.4.6', id: '6' }, { path: '3.4.7', id: '7' }, { path: '8', id: '8' }],
tree = getTree(input),
flat = getFlat(tree);
console.log(tree);
console.log(flat);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Categories