How to Build a Dynamic object on JS From two arrays - javascript

I'm trying to build an array of objects from another two arrays, can someone help me out.
const parent =[
{ Id: 1, Cate: 'Accommodation', p_id: null },
{ Id: 4, Cate: 'National Travel', p_id: null }
]
const child =[
{ Id: 2, Cate: 'Hotel Accommodation', p_id: 1 },
{ Id: 3, Cate: 'Own arrangement', p_id: 1 },
{ Id: 5, Cate: 'Air', p_id: 4 },
{ Id: 6, Cate: 'Bus', p_id: 4 },
{ Id: 7, Cate: 'AC Volvo', p_id: 6 },
{ Id: 8, Cate: 'Luxury Bus', p_id: 6 },
{ Id: 9, Cate: 'Bus', p_id: 6 }
]
const data = [
{
id: 1,
tittle: 'Accommodation',
subItem: [
{ id: 2, tittle: 'Hotel Accommodation', subItems: [] },
{ id: 3, tittle: 'Own arrangement', subItems: [] },
],
},
{
id: 4,
tittle: 'National Travel',
subItem: [
{ id: 5, tittle: 'Air', subItems: [] },
{
id: 6,
tittle: 'Bus',
subItem: [
{ id: 7, tittle: 'AC Volvo' },
{ id: 5, tittle: 'Air' },
{ id: 8, tittle: 'Luxury Bus' },
{ id: 9, tittle: 'Bus' },
],
},
],
},
];
parent and child are the data I get from DB, now I need to change that into something looking like data. The thing is it has to be dynamic, as subItems can go long as it needs to be, how can I make something like that?

You can maintain two objects, a parent object, and a seen object. The seen object is responsible for storing all object ids already seen. That means, for each object in your array, you add it to the seen object.
// Seen object
{
id_1: {id: id_1, ...}, /* id: 1 */
id_2: {id: id_2, ...}, /* id: 2 */
id_3: {id: id_3, ...}, /* id: 3 */
id_4: {id: id_3, ...} /* id: 4 */
}
As your parent objects come before they are used by your child objects, you can say that, when the p_id does not appear in the seen object, then the current object is a parent, and so, you can add its id to the parents object (in the above example, assume id_1 is a parent). Keep in mind that this object also gets added to seen, and so, we can create a link between the parent object in seen and the one in parents by using its reference:
// Parents object
{
id_1: /* ref: 1 */ <-- this refers to the seen[id_1] object
}
This link then allows us to change the object in seen, which would then result in the object in parents changing.
When an object is not a parent object, then it must be a child of another object we have already seen. In this example, assume that the object with id_2 is a child/subItem of id_1. We can first find id_2's parent object (ie: the object with id_1) by looking it up in the seen object using seen[p_id]. Once we have the parent object for id_2, we can add a subItem array to it if needed, and add a reference to our id_2 object to this array:
// Seen object
{
id_1: {id: id_1, ..., subItem: [/* ref: 2 */]}, /* id: 1 */
id_2: {id: id_2, ...}, /* id: 2 */
}
we add id_2 as a reference by pushing in the object we added to seen by using .push(seen[Id]). We're adding a reference, as this will mean that if we change the object stored at id_2 in our seen object, then its changes will be reflected in id_1's subItem array.
Once we have iterated over all objects in our array, we can grab the values of our parents object, which contains references to the objects in seen, and those objects themselves also (may) contain references to other objects in seen. This gives us our final result:
const parent =[ { Id: 1, Cate: 'Accommodation', p_id: null }, { Id: 4, Cate: 'National Travel', p_id: null } ];
const child = [ { Id: 2, Cate: 'Hotel Accommodation', p_id: 1 }, { Id: 3, Cate: 'Own arrangement', p_id: 1 }, { Id: 5, Cate: 'Air', p_id: 4 }, { Id: 6, Cate: 'Bus', p_id: 4 }, { Id: 7, Cate: 'AC Volvo', p_id: 6 }, { Id: 8, Cate: 'Luxury Bus', p_id: 6 }, { Id: 9, Cate: 'Bus', p_id: 6 } ];
const buildChildren = arr => {
const parents = {}, seen = {};
for(const {Id, Cate, p_id} of arr) {
seen[Id] = {id: Id, title: Cate};
if(p_id in seen) {
const myParent = seen[p_id];
myParent.subItem = myParent.subItem || []; // add subItem property if it doesn't exist yet
myParent.subItem.push(seen[Id]);
} else { // Adding a new parent
parents[Id] = seen[Id];
}
}
return Object.values(parents);
}
console.log(buildChildren([...parent, ...child]));

The more concise and another detailed explanation (Based on #Nick
Parsons's idea)
Besides, the problem is that item.Id make sure that it should be sorted ascending.
The main ideas are:
Push the "parent item" into result.
//If the first meet item with `parent ID` doesn't exist in store yet, then it should be the parent item
if(!parentItemInStore)
result.push(store[Id]); // Adding a new parent
Add subItem into the "parent item". By using the Reference Javascript, we can modify the item as well as reflect the reference item.
else { // add subItem
parentItemInStore.subItem ??= []; // Using `logical nullish assignment` to initialize subItem
parentItemInStore.subItem.push(store[Id]);
}
const parent =[ { Id: 1, Cate: 'Accommodation', p_id: null }, { Id: 4, Cate: 'National Travel', p_id: null } ];
const child = [ { Id: 2, Cate: 'Hotel Accommodation', p_id: 1 }, { Id: 3, Cate: 'Own arrangement', p_id: 1 }, { Id: 5, Cate: 'Air', p_id: 4 }, { Id: 7, Cate: 'AC Volvo', p_id: 6 }, { Id: 6, Cate: 'Bus', p_id: 4 }, { Id: 8, Cate: 'Luxury Bus', p_id: 6 }, { Id: 9, Cate: 'Bus', p_id: 6 } ];
const buildHierarchyCollection = flatItems => {
flatItems.sort((a, b) => a.Id - b.Id); // this line is extremely important
const result = [], store = {};
for(const {Id, Cate, p_id} of flatItems) {
store[Id] = {Id, Cate}; // Store each item to keep the reference
const parentItemInStore = store[p_id];
if(!parentItemInStore) // If the first meet item with `parent ID` doesn't exist in store yet, then it should be the parent item
result.push(store[Id]); // Adding a new parent
else { // add subItem
parentItemInStore.subItem ??= []; // Using `logical nullish assignment` to initialize subItem
parentItemInStore.subItem.push(store[Id]);
}
}
return result;
}
console.log(buildHierarchyCollection([...parent, ...child]));
Solution 2: No need to sort
const parent =[ { Id: 1, Cate: 'Accommodation', p_id: null }, { Id: 4, Cate: 'National Travel', p_id: null } ];
const child = [ { Id: 2, Cate: 'Hotel Accommodation', p_id: 1 }, { Id: 3, Cate: 'Own arrangement', p_id: 1 }, { Id: 5, Cate: 'Air', p_id: 4 }, { Id: 7, Cate: 'AC Volvo', p_id: 6 }, { Id: 6, Cate: 'Bus', p_id: 4 }, { Id: 8, Cate: 'Luxury Bus', p_id: 6 }, { Id: 9, Cate: 'Bus', p_id: 6 } ];
const resolveParrentMissing = (store, currentItem) => {
for(let [key, value] of Object.entries(store))
if(value.p_id == currentItem.Id) {
currentItem.subItem ??= [];
currentItem.subItem.push(value);
}
}
const buildHierarchyCollection = flatItems => {
const result = [], store = {};
for(const {Id, Cate, p_id} of flatItems) {
store[Id] = {Id, Cate, p_id}; // Store each item to keep the reference
const parentItemInStore = store[p_id];
if(!p_id) // If the first meet item with `parent ID` doesn't exist in store yet, then it should be the parent item
result.push(store[Id]); // Adding a new parent
else if(parentItemInStore) { // add subItem
parentItemInStore.subItem ??= []; // Using `logical nullish assignment` to initialize subItem
parentItemInStore.subItem.push(store[Id]);
}
resolveParrentMissing(store, store[Id]);
}
return result;
}
const result = buildHierarchyCollection([...parent, ...child]);
console.log(result.map(({p_id, ...rest}) => rest));

I think this approach is simpler than many others presented here.
const makeForest = (xs, id) =>
xs .filter ((x) => x.p_id == id)
.map (({Id, Cate, p_id}, _, __, subItems = makeForest (xs, Id)) => ({
id: Id, tittle: Cate, ... (subItems.length ? {subItems} : {})
}))
const parent = [{Id: 1, Cate: 'Accommodation', p_id: null}, {Id: 4, Cate: 'National Travel', p_id: null}]
const child = [{Id: 2, Cate: 'Hotel Accommodation', p_id: 1}, {Id: 3, Cate: 'Own arrangement', p_id: 1}, {Id: 5, Cate: 'Air', p_id: 4}, {Id: 6, Cate: 'Bus', p_id: 4}, {Id: 7, Cate: 'AC Volvo', p_id: 6}, {Id: 8, Cate: 'Luxury Bus', p_id: 6}, {Id: 9, Cate: 'Bus', p_id: 6}]
console .log (
makeForest ([...parent, ...child])
)
.as-console-wrapper {max-height: 100% !important; top: 0}
We start by combining the two arrays outside our main call. This sort of code is easier to work with on a single list.
We filter out those items whose p_ids match an input parameter (and if we don't pass one, then the ones with null values will also match. Then for each one of those, we build a new object, with subItems being created with a recursive all, using the same list and the current id.
If there are other fields we want to copy over directly, we can just destructure out a ...rest parameter and include it in our mapping:
const makeForest = (xs, id) =>
xs .filter ((x) => x.p_id == id)
.map (({Id, Cate, p_id, ...rest}, _, __, subItems = makeForest (xs, Id)) => ({
id: Id, tittle: Cate, ... rest, ... (subItems.length ? {subItems} : {})
}))
Update
Nguyễn Văn Phong pointed out this simplification:
const makeForest = (xs, id) =>
xs .filter ((x) => x.p_id == id)
.map (({Id, Cate, p_id, subItems = makeForest (xs, Id)}) => ({
id: Id, tittle: Cate, ... (subItems.length ? {subItems} : {})
}))
which would work equally well with the ...rest case. It's very nice not to need those placeholder variables for the map call.

const parent =[ { Id: 1, Cate: 'Accommodation', p_id: null }, { Id: 4, Cate: 'National Travel', p_id: null } ];
const child = [ { Id: 2, Cate: 'Hotel Accommodation', p_id: 1 }, { Id: 3, Cate: 'Own arrangement', p_id: 1 }, { Id: 5, Cate: 'Air', p_id: 4 }, { Id: 6, Cate: 'Bus', p_id: 4 }, { Id: 7, Cate: 'AC Volvo', p_id: 6 }, { Id: 8, Cate: 'Luxury Bus', p_id: 6 }, { Id: 9, Cate: 'Bus', p_id: 6 } ];
function prepardata(parent, child) {
// map to hold all the values of parent and child arrays
const map = {};
// map to hold final result
let item = {};
// add parent elements to map
for (let i = 0; i < parent.length; i++) {
map[parent[i].Id] = parent[i];
}
// add child elements to map
for (let i = 0; i < child.length; i++) {
// adding empty subItems array to every child category
child[i]["subItems"] = [];
map[child[i].Id] = child[i];
}
/**
* At this point we have map ready with key as Id and value as
* the object.
* Using for-in to iterate over the map where key is Id
*/
for (let key in map) {
// if map has a key with value of the current entry p_id (map[key].p_id))
if (map.hasOwnProperty(map[key].p_id)) {
let parentCat = map[map[key].p_id]; // parent category element is the entry in map with value equal to p_id
let childCat = map[key]; // child category element is the entry in map with value equal to key
// Elements with p_id as null should be at the top level
if (!parentCat.p_id) {
// If final item map does not have an entry, then add it
if (!item[parentCat.Id]) {
item[parentCat.Id] = {
id: parentCat.Id,
title: parentCat.Cate,
subItems: [childCat]
}
} else {
// else just push the element in the existing parent categories subitems
item[parentCat.Id]["subItems"].push(childCat);
}
} else {
// Else it's a sub category, the item needs to be pushed under its subItems array
if (item[parentCat.p_id]) {
item[parentCat.p_id]["subItems"].filter(s => s.Id === childCat.p_id)[0]["subItems"].push(childCat);
}
}
}
}
// finally return the values from item map.
return Object.values(item);
}
const data = prepardata(parent, child);
console.log(data);
You can use javascript map and filter to achieve this result. Iterate over the parent array to get the id and title and filter the child array to get only items with matching p_id.
const data = [];
parent.map(p => {
const item = {
id: p.Id,
title: p.Cate,
subItem: child.filter(c => c.p_id === p.Id)
}
data.push(item);
});
Edit
I've added snippet with the updated solution which supports multiple levels as my initial answer only supported 2 levels. Accepted answer by #Nick Parsons is correct and also shorter version but I'm still updating this answer to complete it.

Related

How to make json with infinite parent child category from database

I have an SQLite database table
+---------------------------------------------------+
| id | Cat_Name | Parent_ID |
|---------------------------------------------------+
| 1 | Asset | NULL |
+---------------------------------------------------+
| 2 | Bank | 1 |
+---------------------------------------------------+
| 3 | Cash | 1 |
+---------------------------------------------------+
| 4 | Petty Cash | 3 |
+---------------------------------------------------+
| 5 | ABC Bank | 2 |
+---------------------------------------------------+
| 6 | Dollar Account | 2 |
+---------------------------------------------------+
i can fetch the data as below
[{ id: 1, Category_Name: "Asset", Parent_ID: 0},
{ id: 2, Category_Name: "Bank", Parent_ID: 1},
{ id: 3, Category_Name: "Cash", Parent_ID: 1},
{ id: 4, Category_Name: "Petty_Cash", Parent_ID: 3},
{ id: 5, Category_Name: "ABC_Bank", Parent_ID: 2},
{ id: 6, Category_Name: "Dollar_Account", Parent_ID: 2}]
In this table, category and subcategory created by the user, we can't assume how many parent and child categories will be in the table
Now I want pass the data as a nested javascript object to the front end
example
{Asset: {Bank: {ABC Bank: 5}, {Dollar Account: 6}
},
{Cash:{PettyCash: 4}, if any...}
}
Could anybody can help to get this result in the best way...
Thanks in advance
I suggest you change the design of the output object. I think the array approach would be better for the frontend.
const rawData = [
{ id: 1, Category_Name: "Asset", Parent_ID: 0},
{ id: 2, Category_Name: "Bank", Parent_ID: 1},
{ id: 3, Category_Name: "Cash", Parent_ID: 1},
{ id: 4, Category_Name: "Petty Cash", Parent_ID: 3},
{ id: 5, Category_Name: "ABC Bank", Parent_ID: 2},
{ id: 6, Category_Name: "Dollar Account", Parent_ID: 2},
{ id: 7, Category_Name: "Another Wallet", Parent_ID: 4},
];
const getParentDeep = (arr, targetId) => arr.find(({ id }) => id === targetId)
?? arr.flatMap(({ children }) => getParentDeep(children, targetId))
.filter(e => e)
.at(0);
const result = rawData
.sort(({ Parent_ID: a }, { Parent_ID: b }) => a - b)
.reduce((acc, { id, Category_Name, Parent_ID }) => {
const obj = { id, name: Category_Name, children: [] };
const parentObj = getParentDeep(acc, Parent_ID);
if (parentObj) parentObj.children.push(obj)
else acc.push(obj);
return acc;
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0 }
The result will look like this:
[{
id: 1,
name: "Asset",
children: [{
id: 2,
name: "Bank",
children: [{
id: 5,
name: "ABC Bank",
children: []
}, {
id: 6,
name: "Dollar Account",
children: []
}]
}, {
id: 3,
name: "Cash",
children: [{
id: 4,
name: "Petty Cash",
children: [{
id: 7,
name: "Another Wallet",
children: []
}]
}]
}]
}]
Presented below is one possible way to achieve the desired objective. Admittedly, it is not very elegant (& possibly not the most-efficient).
Code Snippet
// helper method to recursively-add to object
const recurAdd = (arr, idx, res) => {
// when "idx" exceeds length of array "arr",
// simply return existing result "res" object
if (idx >= arr.length) return res;
// de-structure to access parent-id & id for current elt
const { Parent_ID, id } = arr[idx];
if (Parent_ID in res) {
// parent-id exists at current object,
// so, add "id" to same object (mutate)
res[Parent_ID][id] = {};
// make recursive call for "next" elt in "arr"
return recurAdd(arr, idx+1, res);
} else {
// find next-level object where current elt will fit
const foundIt = Object.values(res).map(obj => recurAdd(arr, idx, obj));
// NOTE: "obj" is part of "res" and it gets mutated
// if found, make recursive call
if (foundIt.some(x => x !== false)) return recurAdd(arr, idx+1, res);
};
// in case parent-id is not found, simply return false
return false;
};
// helper method to substitute "id" with "category names"
const recurNamify = (obj, myMap) => (
// reconstruct object from key-value pairs of intermediate result
Object.fromEntries(
// generate intermediate result of key-value pairs
Object.entries(obj)
.map(([k, v]) => (
// substitute key (ie, "id") with category-name
Object.keys(v).length === 0
? [myMap[k], k]
: [myMap[k], recurNamify(v, myMap)]
))
// when "v" is not an empty object, make recursive call
)
);
// transform the array into nested object
const myTransform = arr => {
// first transform "Number" to "string" for id and parent-id
// because JS-object keys are string type
const myArr = arr.map(ob => ({
...ob,
id: ob.id.toString(),
Parent_ID: ob.Parent_ID.toString()
}));
// generate a dictionary/map for "id" to category-name
const myMap = myArr.reduce(
(acc, itm) => {
acc[itm.id] = itm.Category_Name
return acc;
},
{}
);
// find the index of root (ie, parent id is zero)
const rIdx = myArr.findIndex(({ Parent_ID }) => Parent_ID === '0');
// obtain the root & mutate "arr" by removing the root
const [root] = myArr.splice(rIdx, 1);
// use the helper methods to transform
return recurNamify(recurAdd(myArr, 0, {[root.id]: {}}), myMap);
};
const rawData = [
{ id: 1, Category_Name: "Asset", Parent_ID: 0},
{ id: 2, Category_Name: "Bank", Parent_ID: 1},
{ id: 3, Category_Name: "Cash", Parent_ID: 1},
{ id: 4, Category_Name: "Petty_Cash", Parent_ID: 3},
{ id: 5, Category_Name: "ABC_Bank", Parent_ID: 2},
{ id: 6, Category_Name: "Dollar_Account", Parent_ID: 2}
];
console.log('transformed: ', myTransform(rawData));
.as-console-wrapper { max-height: 100% !important; top: 0 }
Explanation
Inline comments added to the snippet above.
PS: If you'd like to add value to stackoverflow community,
Please consider reading: What to do when my question is answered
Thank you !
Here's another linked list variation, but with bi-directional object references and JSON de-/serialization in acknowledgement of the client/server relationship:
The Stack Overflow code snippet virtual console doesn't show interactive object relationships like your browser's JS console, so copy and paste this into your JS console to see the relational references in the final linked list value.
/** Conceptually similar to CSV when stringified, but preserves JSON types */
function compact (keysOrMappedKeys, array) {
const inputKeys = [];
let outputKeys = [];
const keysAreMapped = Array.isArray(keysOrMappedKeys[0]);
if (keysAreMapped) {
for (const [keyIn, keyOut] of keysOrMappedKeys) {
inputKeys.push(keyIn);
outputKeys.push(keyOut);
}
}
else {
for (const key of keysOrMappedKeys) inputKeys.push(key);
outputKeys = inputKeys;
}
const rows = [];
for (const obj of array) {
const row = [];
for (const key of inputKeys) row.push(obj[key]);
rows.push(row);
}
return [outputKeys, rows];
}
// Not actually needed for this answer:
/** The reverse of the `compact` function */
function expand ([keys, rows]) {
return rows.map(array => {
const obj = {};
for (const [index, key] of keys.entries()) obj[key] = array[index];
return obj;
});
}
/** Expects keys in the order `[ownId, parentId, ...others]` */
function createLinkedObjectList ([keys, rows]) {
const map = new Map(rows.map(row => {
const obj = {};
const iter = keys.entries();
const [ownIdIndex] = iter.next().value;
const ownId = row[ownIdIndex];
const [parentIdIndex] = iter.next().value;
const parentId = row[parentIdIndex];
for (const [index, key] of iter) obj[key] = row[index];
return [ownId, {id: ownId, parentId, value: obj}];
}));
for (const obj of map.values()) {
const parent = map.get(obj.parentId);
if (typeof parent !== 'undefined') {
obj.parent = parent;
(parent.children ??= []).push(obj);
}
delete obj.parentId;
}
return [...map.values()];
}
// Use: On the server:
// From the SQLite db:
const input = [
{ id: 1, Category_Name: "Asset", Parent_ID: 0},
{ id: 2, Category_Name: "Bank", Parent_ID: 1},
{ id: 3, Category_Name: "Cash", Parent_ID: 1},
{ id: 4, Category_Name: "Petty_Cash", Parent_ID: 3},
{ id: 5, Category_Name: "ABC_Bank", Parent_ID: 2},
{ id: 6, Category_Name: "Dollar_Account", Parent_ID: 2},
];
// Optionally, rename the keys when compacting the data structure:
const mappedKeys = [
['id', 'id'], // The ID key needs to be first
['Parent_ID', 'parent'], // The parent ID key needs to be second
// The order of the remaining keys is simply preference:
['Category_Name', 'name'],
];
const compacted = compact(mappedKeys, input);
/*
Or, just use the original key names:
const keys = [
'id', // The ID key needs to be first
'Category_Name', // The parent ID key needs to be second
// The order of the remaining keys is simply preference:
'Parent_ID',
];
const compacted = compact(keys, input);
*/
// You can send this JSON string to the client
const json = JSON.stringify(compacted);
console.log(json); // [["id","parent","name"],[[1,0,"Asset"],[2,1,"Bank"],[3,1,"Cash"],[4,3,"Petty_Cash"],[5,2,"ABC_Bank"],[6,2,"Dollar_Account"]]]
// Use: On the client:
/* After receiving the json from the server:
const json = await getDataFromServer();
Expand it into a linked list with bi-directional references
between actual parent and children objects.
This is where the order of the keys matters: */
const list = createLinkedObjectList(compacted);
console.log(list); /* Looks like this:
[
{
id: 1,
value: { name: 'Asset' },
children: [
{ id: 2, ... },
{ id: 3, ... },
],
},
{
id: 2,
value: { name: 'Bank' },
parent: { id: 1, ... },
children: [
{ id: 5, ... },
{ id: 6, ... },
],
},
{
id: 3,
value: { name: 'Cash' },
parent: { id: 1, ... },
children: [
{ id: 4, ... },
],
},
{
id: 4,
value: { name: 'Petty_Cash' },
parent: { id: 3, ... },
},
{
id: 5,
value: { name: 'ABC_Bank' },
parent: { id: 2, ... },
},
{
id: 6,
value: { name: 'Dollar_Account' },
parent: { id: 2, ... },
},
]
*/

How to make recursive function vue.js to make data from children

I am using vue.js and v-tree + vue2vis network,
I have a tree with all my items like this one :
items: [
{
id: 1,
name: 'root',
children: [
{
id: 2,
name: 'child1',
children: [
{
id: 3,
name: 'child3',
},
{
id: 4,
name: 'child34',
},
],
},
{
id: 5,
name: 'subroot',
children: [
{
id: 6,
name: 'Mike',
children:[
{
id: 7,
name: 'Mini Mike',
}
]
},
{
id: 8,
name: 'Hunt',
},
],
},
{
id: 9,
name: 'test',
children: [
{
id: 10,
name: 'Brandon',
},
{
id: 11,
name: 'Sean',
},
],
},
],
},
],
And what i want to do is when i click on a item in the tree it will generate data for the network like this:
nodes: [{'id':1 , label: 'root'},{'id':2 , label: 'child1'},{'id':3 , label: 'child3'}]
and so on for all the children and parents
same goes for the edage i want to create conection between parent and child
edage: [{'from': 1, 'to':2},{'from': 2, 'to':3}]
I try this function for this idea but its not working
makeNetConnection(items , itemKey) {
//items -> all the tree data
//itemKey-> its the item i click on the tree that i want to create the view
for (let item of items) {
if (item.name == itemKey) {
this.nodes.push({'id':item.id , 'label':item.name});
return item;
}
if (item.children) {
let i = this.makeNetConnection(item.children ,itemKey)
if (i) {
this.nodes.push({'id':item.id , 'label':item.name});
this.edges.push({'from': item.id , 'to': i.id});
return i;
}
}
}
its duplicate the data in the arrays and not make connect with the parent
i expect to have [{'from': 1, 'to':2},{'from': 2, 'to':3},{'from': 2, 'to':4}]
and os on for all the items
but i have [{'from': 1, 'to':2},{'from': 1, 'to':3} , {'from': 1, 'to':2},{'from': 1, 'to':3}, {'from': 1, 'to':4}, {'from': 1, 'to':4}]
i dont get the middel connection
any idea how to make its work?
Maybe overkill, but use traverse
const traverse = require('traverse');
const returnData=[];
traverse.forEach(function(item){
returnData.push({id:item.id,label:item.value});
});
I would build this upon simpler functions to collect the nodes and to collect the edges. It means running two traversals of your tree, but it makes for much simpler code.
Here we have a recursive function to flatten the nodes of a tree into an array, transforming the name property to a label one as we go. And we have a second recursive function to collect the edges as from-to pairs.
Then we write a very simple function to combine them:
const collectNodes = (xs) =>
xs .flatMap (({id, name, children = []}) => [
{id, label: name},
... collectNodes (children)
])
const collectEdges = (xs) =>
xs .flatMap (({id: from, children = []}) => [
... children .map (({id: to}) => ({from, to})),
... collectEdges (children),
])
const collect = (items) => ({
nodes: collectNodes (items),
edges: collectEdges (items)
})
const items = [{id: 1, name: "root", children: [{id: 2, name: "child1", children: [{id: 3, name: "child3"}, {id: 4, name: "child34"}]}, {id: 5, name: "subroot", children: [{id: 6, name: "Mike", children: [{id: 7, name: "Mini Mike"}]}, {id: 8, name: "Hunt"}]}, {id: 9, name: "test", children: [{id: 10, name: "Brandon"}, {id: 11, name: "Sean"}]}]}]
console .log (collect (items))
.as-console-wrapper {max-height: 100% !important; top: 0}
Could we do this in a single traversal? Certainly, but I think it would make for more convoluted code. I would only bother doing so if the performance of this is not acceptable.
I ignored this: "when i click on a item in the tree". I'm assuming that you want to convert all the data. If you only want it up to a certain node, then please add a clarification to the question.

Create nested array in Javascript

I'm trying to convert my data from API to my needs. Would like to create a nested array from plain array. I would like to group elements by parentId property, if parentId would not exist I would put it as a root. id value is unique. Like so (raw data):
[
{id: 1, name: 'sensor'},
{id: 2, name: 'sensor', parent: 1},
{id: 3, name: 'sensor', parent: 1},
{id: 4, name: 'sensor', parent: 3},
{id: 5, name: 'sensor'},
{id: 6, name: 'sensor', parent: 5}
]
Converted Data:
const results = [
{
id: 1,
name: "sensor",
children: [
{ id: 2, name: "sensor", parent: 1 },
{
id: 3,
name: "sensor",
parent: 1,
children: [{ id: 4, name: "sensor", parent: 3 }]
}
]
},
{ id: 5, name: "sensor", children: [{ id: 6, name: "sensor", parent: 5 }] }
];
I found this recursive method but it assumes that the parent property exist for every element in an array. In my example root level element would not have parent property.
function getNestedChildren(arr, parent) {
var out = []
for(var i in arr) {
if(arr[i].parent == parent) {
var children = getNestedChildren(arr, arr[i].id)
if(children.length) {
arr[i].children = children
}
out.push(arr[i])
}
}
return out
}
You could take an approach which uses both relations, one from children to parent and vice versa. At the end take the children of the root node.
This approach works for unsorted data.
var data = [{ id: 1, name: 'sensor' }, { id: 2, name: 'sensor', parent: 1 }, { id: 3, name: 'sensor', parent: 1 }, { id: 4, name: 'sensor', parent: 3 }, { id: 5, name: 'sensor' }, { id: 6, name: 'sensor', parent: 5 }],
tree = function (data, root) {
var t = {};
data.forEach(o => {
Object.assign(t[o.id] = t[o.id] || {}, o);
t[o.parent] = t[o.parent] || {};
t[o.parent].children = t[o.parent].children || [];
t[o.parent].children.push(t[o.id]);
});
return t[root].children;
}(data, undefined);
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Given the limited amount of information (will update if more info is added).
The algorithm would be, given an array of data entries, check if entry has a parent and if that parent exists, in which case we want to add the entry to the array of children of the parent entry otherwise add the entry as a parent.
var dataFromAPI = [
{id: 1, name: 'sensor'},
{id: 2, name: 'sensor', parent: 1},
{id: 3, name: 'sensor', parent: 1},
{id: 4, name: 'sensor', parent: 3},
{id: 5, name: 'sensor'},
{id: 6, name: 'sensor', parent: 5}
];
var transformedData = { };
dataFromAPI.forEach(function(entry){
if(entry.parent !== undefined && entry.parent in transformedData) {
transformedData[entry.parent].children.push(entry);
} else {
entry["children"] = [];
transformedData[entry.id] = entry;
}
});
console.log(transformedData);
Please note:
there are a couple assumptions made within this algorithm/code. It assumes that all parent entries exist before their child entry. It also only accounts for two levels (parent or child), meaning a child cannot act as the parent (otherwise you'd have to store the children as an object and not an array)
use a for loop to go through each item.
check if parent property exists (or has value).
If not its a child item. Attach it to appropriate parent.
to check if property exists:
var myProp = 'prop';
if (myObj.hasOwnProperty(myProp)) {
alert("yes, i have that property");
}
try
let h={}, r=[]; // result in r
d.forEach(x=> (h[x.id]=x, x.children=[]) );
d.forEach(x=> x.parent ? h[x.parent].children.push(x) : r.push(x) );
let d = [
{id: 1, name: 'sensor'},
{id: 2, name: 'sensor', parent: 1},
{id: 3, name: 'sensor', parent: 1},
{id: 4, name: 'sensor', parent: 3},
{id: 5, name: 'sensor'},
{id: 6, name: 'sensor', parent: 5}
];
let h = {},r = []; // result in r
d.forEach(x => (h[x.id] = x, x.children = []));
d.forEach(x => x.parent ? h[x.parent].children.push(x) : r.push(x));
console.log(r);
If you want that parent element should not have a parent , than you can manually check and remove fields of an object in an array that has null parent. than you can make a tree... here is an example...
const arr2 = [
{id: 1, name: 'gender', parent: null, parent_id: null },
{id: 2, name: 'material', parent: null, parent_id: null },
{id: 3, name: 'male', parent: 1, parent_name: "gender" },
{ id: 5, name: 'female', parent: 1, parent_name: "gender" },
{ id: 4, name: 'shoe', parent: 3, parent_id: "male"},
]
let newarr=[];
for(let i=0 ; i< arr2.length; i++ ){
if(arr2[i].id){
if(newarr[i] != {} ){
newarr[i] = {}
}
newarr[i].id = arr2[i].id
}
if( arr2[i].name ){
newarr[i].name = arr2[i].name
}
if( arr2[i].parent ){
newarr[i].parent = arr2[i].parent
}
if( arr2[i].parent_id ){
newarr[i].parent_id = arr2[i].parent_id
}
}
console.log('newarr', newarr );
let tree = function (data, root) {
var obj = {};
data.forEach(i => {
Object.assign(obj[i.id] = obj[i.id] || {}, i);
obj[i.parent] = obj[i.parent] || {};
obj[i.parent].children = obj[i.parent].children || [];
obj[i.parent].children.push(obj[i.id]);
});
return obj[root].children;
}(newarr, undefined);
console.log('tree ', tree);

Remove matched object from deeply nested array of objects

I have a data tree structure with children:
{ id: 1,
name: "Dog",
parent_id: null,
children: [
{
id: 2,
name: "Food",
parent_id: 1,
children: []
},
{
id: 3,
name: "Water",
parent_id: 1,
children: [
{
id: 4,
name: "Bowl",
parent_id: 3,
children: []
},
{
id: 5,
name: "Oxygen",
parent_id: 3,
children: []
},
{
id: 6,
name: "Hydrogen",
parent_id: 3,
children: []
}
]
}
]
}
This represents a DOM structure that a user could select an item from to delete by clicking the corresponding button in the DOM.
I have a known text title of the selected item for deletion from the DOM set as the variable clickedTitle. I am having trouble finding an algorithm that will allow me to delete the correct object data from the deeply nested tree.
Here is my code:
function askUserForDeleteConfirmation(e) {
const okToDelete = confirm( 'Are you sure you want to delete the item and all of its sub items?' );
if(!okToDelete) {
return;
}
const tree = getTree(); // returns the above data structure
const clickedTitle = getClickedTitle(e); // returns string title of clicked on item from DOM - for example "Dog" or "Bowl"
const updatedTree = removeFromTree(tree, tree, clickedTitle);
return updatedTree;
}
function removeFromTree(curNode, newTree, clickedTitle) {
if(curNode.name === clickedTitle) {
// this correctly finds the matched data item to delete but the next lines don't properly delete it... what to do?
const index = curNode.children.findIndex(child => child.name === clickedTitle);
newTree = curNode.children.slice(index, index + 1);
// TODO - what to do here?
}
for(const node of curNode.children) {
removeFromTree(node, newTree, clickedTitle);
}
return newTree;
}
I have tried to use the info from Removing matched object from array of objects using javascript without success.
If you don't mind modifying the parameter tree in-place, this should do the job. Note that it'll return null if you attempt to remove the root.
const tree = { id: 1, name: "Dog", parent_id: null, children: [ { id: 2, name: "Food", parent_id: 1, children: [] }, { id: 3, name: "Water", parent_id: 1, children: [ { id: 4, name: "Bowl", parent_id: 3, children: [] }, { id: 5, name: "Oxygen", parent_id: 3, children: [] }, { id: 6, name: "Hydrogen", parent_id: 3, children: [] } ] } ] };
const removeFromTree = (root, nameToDelete, parent, idx) => {
if (root.name === nameToDelete) {
if (parent) {
parent.children.splice(idx, 1);
}
else return null;
}
for (const [i, e] of root.children.entries()) {
removeFromTree(e, nameToDelete, root, i);
}
return tree;
};
console.log(removeFromTree(tree, "Oxygen"));
Your current code is very much on the right track. However:
newTree = curNode.children.slice(index, index + 1);
highlights a few issues: we need to manipulate the parent's children array to remove curNode instead of curNode's own children array. I pass parent objects and the child index recursively through the calls, saving the trouble of the linear operation findIndex.
Additionally, slicing from index to index + 1 only extracts one element and doesn't modify curNode.children. It's not obvious how to go about using newArray or returning it through the call stack. splice seems like a more appropriate tool for the task at hand: extracting one element in-place.
Note that this function will delete multiple entries matching nameToDelete.
I like #VictorNascimento's answer, but by applying map then filter, each children list would be iterated twice. Here is an alternative with reduce to avoid that:
function removeFromTree(node, name) {
return node.name == name
? undefined
: {
...node,
children: node.children.reduce(
(children, child) => children.concat(removeFromTree (child, name) || []), [])
}
}
In the case you want a way to remove the items in-place, as #ggorlen proposed, I'd recommend the following solution, that is simpler in my opinion:
function removeFromTree(node, name) {
if (node.name == name) {
node = undefined
} else {
node.children.forEach((child, id) => {
if (!removeFromTree(child, name)) node.children.splice(id, 1)
})
}
return node
}
I've built the algorithm as follows:
function omitNodeWithName(tree, name) {
if (tree.name === name) return undefined;
const children = tree.children.map(child => omitNodeWithName(child, name))
.filter(node => !!node);
return {
...tree,
children
}
}
You can use it to return a new tree without the item:
noHydrogen = omitNodeWithName(tree, "Hydrogen")
If it's ok to use Lodash+Deepdash, then:
let cleaned = _.filterDeep([tree],(item)=>item.name!='Hydrogen',{tree:true});
Here is a Codepen
We use object-scan for many data processing tasks. It's powerful once you wrap your head around it. Here is how you could answer your question
// const objectScan = require('object-scan');
const prune = (name, input) => objectScan(['**[*]'], {
rtn: 'bool',
abort: true,
filterFn: ({ value, parent, property }) => {
if (value.name === name) {
parent.splice(property, 1);
return true;
}
return false;
}
})(input);
const obj = { id: 1, name: 'Dog', parent_id: null, children: [{ id: 2, name: 'Food', parent_id: 1, children: [] }, { id: 3, name: 'Water', parent_id: 1, children: [{ id: 4, name: 'Bowl', parent_id: 3, children: [] }, { id: 5, name: 'Oxygen', parent_id: 3, children: [] }, { id: 6, name: 'Hydrogen', parent_id: 3, children: [] }] }] };
console.log(prune('Oxygen', obj)); // return true iff pruned
// => true
console.log(obj);
// => { id: 1, name: 'Dog', parent_id: null, children: [ { id: 2, name: 'Food', parent_id: 1, children: [] }, { id: 3, name: 'Water', parent_id: 1, children: [ { id: 4, name: 'Bowl', parent_id: 3, children: [] }, { id: 6, name: 'Hydrogen', parent_id: 3, children: [] } ] } ] }
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.8.0"></script>
Disclaimer: I'm the author of object-scan

How to get parent id in unlimited nested array in JavaScript

It is as the title, but I am facing a problem!
I want to create getParentId(array, id) function.
This function get parent id by child id.
const array = [{
id: 1,
title: 'hello',
children: [{
id: 3,
title: 'hello',
children: [{
id: 4,
title:'hello',
children: [
{ id: 5, title: 'hello'},
{ id: 6, title: 'hello'}
]
},
{
id: 7,
title: 'hello'
}]
}]
},
{
id: 2,
title: 'hello',
children: [
{ id: 8, title: 'hello'}
]
}]
This array may nest indefinitely
Expected Result:
getParentId(array, 3) -> 1
getParentId(array, 5) -> 4
getParentId(array, 6) -> 4
getParentId(array, 8) -> 2
getParentId(array, 2) -> null
I would be grateful if you would send me information.
You could take a recursive approach by iterating the actual array and their children and stop if the id is found.
function getParentId(array, id, parentId = null) {
return array.some(o => {
if (o.id === id) return true;
const temp = getParentId(o.children || [], id, o.id);
if (temp !== null) {
parentId = temp;
return true;
}
})
? parentId
: null;
}
const array = [{ id: 1, title: 'hello', children: [{ id: 3, title: 'hello', children: [{ id: 4, title:'hello', children: [{ id: 5, title: 'hello' }, { id: 6, title: 'hello' }] }, { id: 7, title: 'hello' }] }] }, { id: 2, title: 'hello', children: [{ id: 8, title: 'hello' }] }];
console.log(getParentId(array, 3)); // 1
console.log(getParentId(array, 5)); // 4
console.log(getParentId(array, 6)); // 4
console.log(getParentId(array, 8)); // 2
console.log(getParentId(array, 2)); // null
console.log(getParentId(array, 7)); // 3
console.log(getParentId(array, 4)); // 3
.as-console-wrapper { max-height: 100% !important; top: 0; }
Nina Scholz's answer is great, but here's a slightly less functional approach (not using Array.some), if you like it better:
const array = [{id: 1, title: 'hello', children: [{id: 3, title: 'hello', children: [{id: 4, title:'hello', children: [{ id: 5, title: 'hello'}, { id: 6, title: 'hello'}]}, {id: 7, title: 'hello'}]}]}, {id: 2, title: 'hello', children: [{ id: 8, title: 'hello'}]}];
function getParentId(array, id, parentId = null) {
// For every entry in the array
for (const entry of array) {
// If the ID matches, return the current parent ID
if (entry.id === id) {
return parentId;
}
// Otherwise, call the same function on its children, passing itself as the parent.
// If there was a match, return it.
if (entry.children && (deeperParentId = getParentId(entry.children, id, entry.id))) {
return deeperParentId;
}
}
// No match was found
return null;
}
console.log(getParentId(array, 3));
console.log(getParentId(array, 5));
console.log(getParentId(array, 6));
console.log(getParentId(array, 8));
console.log(getParentId(array, 2));
Note that I overcommented it, which is not such a good idea when writing actual code; this is just for the answer.
Also, as I mentioned in the comments, please share your attempts next time.

Categories

Resources