How To Build Javascript Recursion Tree - javascript

Sorry my english is not good, hope everyone understand. I have an array:
var data=[
{
"id": 2,
"parent_id": 1
},
{
"id": 3,
"parent_id": 2
},
{
"id": 7,
"parent_id": 3
},
{
"id": 67,
"parent_id": 1
}
]
And this is what I need the result to look:
[
{
"id": 2,
"parent_id": 1,
"child":[
{
"id": 3,
"parent_id": 2,
"child":[{
"id": 7,
"parent_id": 3
},
]}
]},
{
"id": 67,
"parent_id": 1
},]
My idea is: 1 method has 2 parameters of the same array. I use nested loop. If parent_id == id will add the field "child".
const getTree = function(data, maindata){
const result=data.forEach(item =>{
const child=maindata.forEach(element =>{
if(item.id === element.parent_id){
return true;
}
return false
})
getTree(child, maindata)
item.child = child;
})
return result;
}
console.log(getTree(data,data))
But it is not working as it should. Hope everybody help please. thanks

I'm not sure what your original code is supposed to do, but you're not getting any results because data.forEach doesn't return anything. You need to first filter out the objects that are children (which I assume is what your original code was aiming to do) and then afterwards assign all the objects to their parents like this:
var data=[{"id": 2,"parent_id": 1},{"id": 3,"parent_id": 2},{"id": 7,"parent_id": 3},{"id": 67,"parent_id": 1},]
const filterData = function(data) {
return data.filter(item => {
let isChild = false;
data.forEach(parent => {
if (parent.id == item.parent_id) {
isChild = true;
return;
}
});
return !isChild;
});
}
const getTree = function(data, maindata){
return data.map(item =>{
let children = [];
maindata.forEach(child => {
if (item.id == child.parent_id) {
children.push(child);
}
});
if (children.length > 0) {
item.child = getTree(children, maindata);
}
return item;
});
}
console.log(getTree(filterData(data),data));

Another way:
var data=[{"id": 2,"parent_id": 1},{"id": 3,"parent_id": 2},{"id": 7,"parent_id": 3},{"id": 67,"parent_id": 1},]
data.sort(byHierarchy);
var dest = [], idx = {};
for (var i of data) {
if (i.parent_id === 1) {
dest.push(i);
} else {
let j = idx[i.parent_id];
if (j.child) j.child.push(i);
else j.child = [i];
}
idx[i.id] = i;
}
function byHierarchy(a, b) {
if (a.parent_id === b.parent_id) return a.id - b.id;
return a.parent_id - b.parent_id;
}
console.log(JSON.stringify(dest, null, 2).replace(/([{[\]}])[\s\n]+([{[\]}])/g, '$1$2'));

I think this is the cleanest way to do it
const data=[{"id": 2,"parent_id": 1},{"id": 3,"parent_id": 2},{"id": 7,"parent_id": 3},{"id": 67,"parent_id": 1},]
let makeTree = (data, parent) => {
let node = []
data
.filter(d => d.parent_id == parent)
.forEach(d => {
d.child = makeTree(data, d.id)
node.push(d)
})
return node
}
console.log(JSON.stringify(makeTree(data,1),null,2))

Related

sort objects array by specific value in different keys

I have this array -
let array = [
{
"id": 123,
"pair": 312
},
{
"id": 321,
"pair": 111
},
{
"id": 312,
"pair": 123
},
{
"id": 111,
"pair": 321
}
];
And i need it to be sorted like this =
let array = [
{
"id": 123,
"pair": 312
},
{
"id": 312,
"pair": 123
},
{
"id": 321,
"pair": 111
},
{
"id": 111,
"pair": 321
}
];
Which means that i need to find the matched value of the pair key in the object, and put it right after the first element (eventually i need the to be sorted in pairs order - of course the array will be way bigger and mixed)
i could not find a efficient way to achieve this.
this is what i tried - it feels very unefficient
products is the array i get from the server.
let pairs = [];
let prods = [...products];
for(let product of prods){
if(product.matched){
continue;
}
let pairStock = product.pairStock;
let companyId = product.company;
let matched = prods.filter(prod => prod.productId === pairStock && String(prod.company) === String(companyId));
if(matched.length > 0){
pairs.push(product);
pairs.push(matched[0]);
let index = prods.findIndex(prod => prod.productId === matched[0].productId);
prods[index].matched = true;
}
};
this will sort data when the number of items that are linked togather is between 0 and array.length.
let products = [
{ productId: 'PK0154', pairStock: 'PK0112-02' },
{ productId: 'PK0112-02', pairStock: 'PK0154' },
{ productId: 'MGS-140', pairStock: 'MGS-136' },
{ productId: 'GM-0168', pairStock: 'GM-0169' },
{ productId: 'GM-0169', pairStock: 'GM-0168' },
{ productId: 'MGS-136', pairStock: 'MGS-140' },
]
function sort(data) {
var mappedArray = {}
data.forEach(obj => (mappedArray[obj.productId] = obj))
data.sort((a, b) => a.productId.localeCompare( b.productId) )
var addToRes = (res, id) => {
if (id !== undefined && mappedArray[id] !== undefined) {
var obj = mappedArray[id]
mappedArray[id] = undefined
res.push(obj)
addToRes(res, obj.pairStock)
}
}
var result = []
data.forEach(item => addToRes(result, item.productId))
return result
}
console.log(sort(products))
its results
0: {productId: "GM-0168", pairStock: "GM-0169"}
1: {productId: "GM-0169", pairStock: "GM-0168"}
2: {productId: "MGS-136", pairStock: "MGS-140"}
3: {productId: "MGS-140", pairStock: "MGS-136"}
4: {productId: "PK0112-02", pairStock: "PK0154"}
5: {productId: "PK0154", pairStock: "PK0112-02"}
The performant way (store a map of products and look up each product's pair by it's id) - O(n*2):
const productsObj = {};
products.forEach(product => productsObj[product.id] = product);
const [seenProducts, pairedProducts] = [{}, []];
products.forEach(product => {
const productPair = productsObj[product.pair);
if (!seenProducts[product.id]) pairedProducts.push(...[product, productPair])
seenProducts[productPair.id] = true;
});
console.log(pairedProducts)
The intuitive way O(n^2):
// Find the next pair if a pair doesn't already exist
const arr = [];
for (let i = 0; i < products.length; i++) {
const containsPair = arr.some(item => item.pair === products[i].id);
if (containsPair === false) {
const productPair = products.slice(i).find(({ id }) => id === item.pair));
arr.push(...[products[i], productPair]);
}
}
console.log(arr)

Map Json data by JavaScript

I have a Json data that I want to have in a different format.
My original json data is:
{
"info": {
"file1": {
"book1": {
"lines": {
"102:0": [
"102:0"
],
"105:4": [
"106:4"
],
"106:4": [
"107:1",
"108:1"
]
}
}
}
}
}
And I want to map it as following:
{
"name": "main",
"children": [
{
"name": "file1",
"children": [
{
"name": "book1",
"group": "1",
"lines": [
"102",
"102"
],
[
"105",
"106"
],
[
"106",
"107",
"108"
]
}
],
"group": 1,
}
],
"group": 0
}
But the number of books and number of files will be more. Here in the lines the 1st part (before the :) inside the "" is taken ("106:4" becomes "106"). The number from the key goes 1st and then the number(s) from the value goes and make a list (["106", "107", "108"]). The group information is new and it depends on parent-child information. 1st parent is group 0 and so on. The first name ("main") is also user defined.
I tried the following code so far:
function build(data) {
return Object.entries(data).reduce((r, [key, value], idx) => {
//const obj = {}
const obj = {
name: 'main',
children: [],
group: 0,
lines: []
}
if (key !== 'reduced control flow') {
obj.name = key;
obj.children = build(value)
if(!(key.includes(":")))
obj.group = idx + 1;
} else {
if (!obj.lines) obj.lines = [];
Object.entries(value).forEach(([k, v]) => {
obj.lines.push([k, ...v].map(e => e.split(':').shift()))
})
}
r.push(obj)
return r;
}, [])
}
const result = build(data);
console.log(result);
The group information is not generating correctly. I am trying to figure out that how to get the correct group information. I would really appreciate if you can help me to figure it out.
You could use reduce method and create recursive function to build the nested structure.
const data = {"info":{"file1":{"book1":{"lines":{"102:0":["102:0"],"105:4":["106:4"],"106:4":["107:1","108:1"]}}}}}
function build(data) {
return Object.entries(data).reduce((r, [key, value]) => {
const obj = {}
if (key !== 'lines') {
obj.name = key;
obj.children = build(value)
} else {
if (!obj.lines) obj.lines = [];
Object.entries(value).forEach(([k, v]) => {
obj.lines.push([k, ...v].map(e => e.split(':').shift()))
})
}
r.push(obj)
return r;
}, [])
}
const result = build(data);
console.log(result);
I couldn't understand the logic behind group property, so you might need to add more info for that, but for the rest, you can try these 2 functions that recursively transform the object into what you are trying to get.
var a = {"info":{"file1":{"book1":{"lines":{"102:0":["102:0"],"105:4":["106:4"],"106:4":["107:1","108:1"]}}}}};
var transform = function (o) {
return Object.keys(o)
.map((k) => {
return {"name": k, "children": (k === "lines" ? parseLines(o[k]) : transform(o[k])) }
}
)
}
var parseLines = function (lines) {
return Object.keys(lines)
.map(v => [v.split(':')[0], ...(lines[v].map(l => l.split(":")[0]))])
}
console.log(JSON.stringify(transform(a)[0], null, 2));

Add element for object

I need to go through a list of objects to find the element and add a new element to the root, I can scroll through the list and find the element, but I can not add to the correct level
var data = [
{
"id": 1
},
{
"id": 2
},
{
"id": 3
},
{
"id": 4,
"children": [
{
"id": 6
},
{
"id": 7
}
]
},
{
"id": 5
}
];
function findById(data, id, element) {
function iter(a) {
if (a.id === id) {
a.push(element); // ERROR
result = a;
return true;
}
return Array.isArray(a.children) && a.children.some(iter);
}
var result;
data.some(iter);
return result
}
var element = {
"children": [{"id": 6}]
};
findById(data, 5, element);
document.write('<pre>' + JSON.stringify(data, 0, 4) + '</pre>');
https://jsfiddle.net/4nrsccnu/
Use Object.assign to merge the properties from the element object to the current object of the iteration.
var data = [
{
"id": 1
},
{
"id": 2
},
{
"id": 3
},
{
"id": 4,
"children": [
{
"id": 6
},
{
"id": 7
}
]
},
{
"id": 5
}
];
function findById(data, id, element) {
function iter(a) {
if (a.id === id) {
Object.assign(a, element);
result = a;
return true;
}
return Array.isArray(a.children) && a.children.some(iter);
}
var result;
data.some(iter);
return result
}
var element = {
"children": [{"id": 6}]
};
findById(data, 5, element);
console.log(data);
You cannot push, because a is an object. However, you can simply add the desired property (in your case, children), and then assign it a value (in your case, the element).
var data = [
{
"id": 1
},
{
"id": 2
},
{
"id": 3
},
{
"id": 4,
"children": [
{
"id": 6
},
{
"id": 7
}
]
},
{
"id": 5
}
];
function findById(data, id, element) {
function iter(a) {
if (a.id === id) {
a.children = element; // Add property 'children'
result = a;
return true;
}
return Array.isArray(a.children) && a.children.some(iter);
}
var result;
data.some(iter);
return result
}
// remove property name from element, as that is being added in the function
var element = [
{"id": 6}
];
findById(data, 5, element);
document.write('<pre>' + JSON.stringify(data, 0, 4) + '</pre>');
The push function is used for arrays, not to object. Check for details https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push.
If you want to add a single keypair to your object, you can use Object.keys and Object.values. Like this:
function findById(data, id, element) {
function iter(a) {
if (a.id === id) {
var key = Object.keys(element)[0];
var value = Object.values(element)[0];
a[key] = value;
result = a;
return true;
}
return Array.isArray(a.children) && a.children.some(iter);
}
var result;
data.some(iter);
return result;
}

Filter array of objects by multiple properties and values

Is it possible to filter an array of objects by multiple values?
E.g in the sample below can I filter it by the term_ids 5 and 6 and type car at the same time?
[
{
"id":1,
"term_id":5,
"type":"car"
},
{
"id":2,
"term_id":3,
"type":"bike"
},
{
"id":3,
"term_id":6,
"type":"car"
}
]
Definitely up for using a library if it makes it easier.
You can do it with Array.filter
var data = [{
"id": 1,
"term_id": 5,
"type": "car"
},
{
"id": 2,
"term_id": 3,
"type": "bike"
},
{
"id": 3,
"term_id": 6,
"type": "car"
}
];
var result = data.filter(function(v, i) {
return ((v["term_id"] == 5 || v["term_id"] == 6) && v.type == "car");
})
console.log(result)
The following function will help you out.
nestedFilter = (targetArray, filters) => {
var filterKeys = Object.keys(filters);
return targetArray.filter(function (eachObj) {
return filterKeys.every(function (eachKey) {
if (!filters[eachKey].length) {
return true;
}
return filters[eachKey].includes(eachObj[eachKey]);
});
});
};
Use this function with filters described as below:
var filters = {
"id": ["3"],
"term_id": ["6"],
"type": ["car","bike"]
}
Dont pass empty array. If there are no values in the array, skip that property in the filters.
The result will be filtered array.
You can do this with plain js filter() method and use && to test for both conditions.
var data = [{"id":1,"term_id":5,"type":"car"},{"id":2,"term_id":3,"type":"bike"},{"id":3,"term_id":6,"type":"car"}];
var result = data.filter(function(e) {
return [5, 6].includes(e.term_id) && e.type == 'car'
});
console.log(result);
Another way to do it is to use lodash filter + reduce.
const arr = [{"id":1,"term_id":5,"type":"car"},{"id":2,"term_id":3,"type":"bike"},{"id":3,"term_id":6,"type":"car"}];
const result = [
{term_id: 5, type: 'car'},
{term_id: 6, type: 'car'},
].reduce((prev, orCondition) => prev.concat(_.filter(arr, orCondition)), []);
console.log(result);
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.21/lodash.min.js"></script>

How to get an JSON Object based in key using jquery

I'm using jsTree and have tree an structured JSON object.
[{
"id": 1,
"text": "TEXT_ONE",
"children": [
{
"id": 2,
"text": "TEXT_TWO",
"children": [
{
"id": 3,
"text": "TEXT_THREE",
"children": [
]
},
{
"id": 4,
"text": "TEXT_FOUR",
"children": [
]
}
]
},
{
"id": 5,
"text": "TEXT_FIVE",
"children": [
]
}
]
},
{
"id": 6,
"text": "TEXT_SIX",
"children": [ ]
}]
I want to get the the object based on the "id" of the object.
For example if i have a function getIdFromTree(3) it will return me the JSON object as following:
{
"id": 3,
"text": "TEXT_THREE",
"children": []
},
How I do that in Javascript/JQuery?
Try this
function getObjById (tree, id) {
if(tree.id === id) {
return tree;
}
if(tree.children) {
for(var i = 0, l = tree.children.length; i < l; i++) {
var returned = getObjById(tree.children[i], id);
if(returned) {
// so that the loop doesn't keep running even after you find the obj
return returned;
}
}
}
}
Call this as follows
getObjById({children: tree}, 3); // tree is the array object above.
function findById (tree, id) {
var result, i;
if (tree.id && tree.id === id) {
result = tree;
// Revalidate array list
} else if (tree.length) {
for (i = 0; i < tree.length; i++) {
result = findById(tree[i], id);
if (result) {
break;
}
}
// Check childrens
} else if (tree.children) {
result = findById(tree.children, id);
}
return result;
}
Use filter Methode off Array
data.filter(function (obj){ obj.id== 3});
try this.... Es6
function *getObjectById(data, id) {
if (!data) return;
for (let i = 0; i< data.length; i++){
let val = data[i];
if (val.id === id) yield val;
if (val.children) yield *getObjectById(val.children , id);
}
}
now
getObjectById(arrayOfObjects, id).next().value;
try this with most effective and efficient way..
function getObjById (tree, id) {
for(var i= 0;i<tree.length;i++)
{
if(tree[i].id===id)
{
return tree[i];
}
if(tree[i].children)
{
var returned = getObjById(tree[i].children,id);
if(returned!= undefined)
return returned;
}
}
};
link:
https://jsfiddle.net/aa7zyyof/14/

Categories