Convert flat data to hierarchical with inner objects javascript - javascript

I faced with common issue converting simple flat data to hierarchical. I have found multiple topics about that but still can't get how to convert flat data exactly to necessary me hierarchical format
this my json
[
{
"id": 1,
"name": "Sponsor",
"description": null,
"parentId": null
},
{
"id": 2,
"name": "Class",
"description": null,
"parentId": 1
},
{
"id": 3,
"name": "Study",
"description": null,
"parentId": 2
},
{
"id": 4,
"name": "Site",
"description": null,
"parentId": 3
}
]
and I need to get format like this
[
{
"data":{
"id": 1,
"name":"Sponsor",
"description":null,
"parentId":"null"
},
"children":[
{
"data":{
"id": 2,
"name":"Class",
"description":null,
"parentId":"1"
},
"children":[
{
"data":{
"id": 3,
"name":"Study",
"description":null,
"parentId":"2"
},
"children": [
{
"data":{
"id": 4,
"name":"Site",
"description":null,
"parentId":"3"
}
}
]
}
]
}
]
}
]
this is my function
flatToHierarchy(flat) {
let roots = [];
let all = {};
flat.forEach(function (item) {
all[item.id] = item
});
Object.keys(all).forEach(function (id) {
let item = all[id];
if (item.parentId === null) {
roots.push(item)
} else if (item.parentId in all) {
let p = all[item.parentId];
if (!('Children' in p)) {
p.children = []
}
p.children.push(item)
}
});
console.log(roots);
return roots
}
output
[
{
"id": 1,
"name": "Sponsor",
"description": null,
"parentId": null,
"children": [
{
"id": 2,
"name": "Class",
"description": "Together",
"parentId": 1,
"children": [
{
"id": 3,
"name": "Study",
"description": "browsing data",
"parentId": 2,
"children": [
{
"id": 4,
"name": "Site",
"description": null,
"parentId": 3,
"children": []
}
]
}
]
}
]
}
]
I'm pretty close to desire result. Could somebody to help me fix that ?
Edited
the right answer provided by #Someone3
this is slightly modified code for my needs
flatToHierarchy (flat) {
let roots = [];
let all = {};
let ids = [];
flat.forEach(function (item) {
let itemId = item.id;
let convertedItem = function (id) {
let newItem = {};
newItem['data'] = id;
return newItem;
} ;
all[itemId] = convertedItem(item);
ids.push(itemId);
});
for (let i = 0; i < ids.length; i++) {
let id = ids[i];
let convertedItem = all[id];
let parentId = convertedItem.data.parentId;
if (parentId === null) {
roots.push(convertedItem);
} else if (parentId in all) {
let p = all[parentId];
if (!('children' in p)) {
p.children = []
}
p.children.push(convertedItem)
}
}
return roots
}

The code below is full source code for your situation. I modified and added a few lines from your source code.
Note that this code assumes that parents are always inserted to this tree before their children do. If this assumption is not always true then your code need to be changed more than this.
let flatData = [
{
"id": 1,
"name": "Sponsor",
"description": null,
"parentId": null
},
{
"id": 2,
"name": "Class",
"description": null,
"parentId": 1
},
{
"id": 3,
"name": "Study",
"description": null,
"parentId": 2
},
{
"id": 4,
"name": "Site",
"description": null,
"parentId": 3
}
];
function convertItem(item) {
let newItem = {};
newItem.data = item;
return newItem;
}
function flatToHierarchy(flat) {
let roots = [];
let all = {};
let ids = [];
flat.forEach(function (item) {
let itemId = item.id;
let convertedItem = convertItem(item);
all[itemId] = convertedItem;
ids.push(itemId);
});
// We use ids array instead of object to maintain its previous order.
for (let i = 0; i < ids.length; i++) {
let id = ids[i];
let convertedItem = all[id];
let parentId = convertedItem.data.parentId;
if (parentId === null) {
delete convertedItem.data.parentId;
delete convertedItem.data.id;
roots.push(convertedItem);
} else if (parentId in all) {
let p = all[parentId];
if (!('Children' in p)) {
p.children = []
}
delete convertedItem.data.parentId;
delete convertedItem.data.id;
p.children.push(convertedItem)
}
};
console.log(roots);
return roots
}
flatToHierarchy(flatData);
We can factor out two deletes before push.

How about my way?
function flatToHierarchy (flatData) {
const tree = JSON.parse(JSON.stringify(flatData)) // or using `cloneDeep` of lodash library to not side-effect with flatData
tree.forEach((item) => {
item.children = tree.filter((element) => element.parent_id === dept.id)
});
const roots = tree.filter((item) => item.parent_id === 0)
return roots
}

const flatToHierarchy = (inputArr, parent = null) => {
const result = [];
for(let i = 0; i<inputArr.length; i++) {
if(inputArr[i].parentId === parent) {
const dataObj = {
data : {
...inputArr[i],
}
}
const children = flatToHierarchy(inputArr, inputArr[i].id);
if(children.length > 0) {
dataObj.children = children;
}
result.push(dataObj);
}
}
return result;
}

Related

Push elements of

This is, I believe a very simple question for a JS programmer. Given the following array of objects named "categories"
[
{
"id": 1,
"name": "FURNITURE",
},
{
"id": 2,
"name": "AUTOMOTIVE",
},
{
"id": 3,
"name": "UPHOLSTERY",
}
]
I want to push the "name" on the "selectedCategories" array below where "id" === "id"
[
{
"id": 1
},
{
"id": 3
}
]
Below is my attempt to solve this, but ... not working..
for (let i = 0; i < selectedCategories.length; i++) {
for(let y = 0; y < categories.length; y++){
selectedCategories.name = categories[y].name
}
}
I believe this is what you are looking for
selectedCategories = selectedCategories.map(el => {
const searchEl = categories.find(e => e.id === el.id);
if (searchEl)
return { ...el, name: searchEl.name }
return el;
});
const categories = [
{
"id": 1,
"name": "FURNITURE",
},
{
"id": 2,
"name": "AUTOMOTIVE",
},
{
"id": 3,
"name": "UPHOLSTERY",
}
];
var selectedCategories = [
{
"id": 1,
},
{
"id": 3,
}
];
selectedCategories = selectedCategories.map(
selectedCategory => categories.find(category => category.id === selectedCategory.id),
);
console.log(selectedCategories);

Nested for loop through json object array does not work

I need to build a new array based on json array (Context) bellow. Unfortunately I never reach the outer Loop after passing by first run. Is there any mistake in code? How can I solve this issue?
Thank you for help.
Context:
"rfqBp": [
{
"rfqBpId": 1041650,
"Contact": [
{
"ID": 1000014,
"SelectedContact": true
},
{
"ID": 1002411,
"SelectedContact": true
},
{
"ID": 1016727,
"SelectedContact": true
},
{
"ID": 1017452,
"SelectedContact": true
}
],
},
{
"rfqBpId": 1052326,
"Contact": [
{
"ID": 1016236,
"SelectedContact": true
},
{
"ID": 1019563,
"SelectedContact": true
}
],
},
{
"rfqBpId": 1056632,
"Contact": [
{
"ID": -1,
"SelectedContact": false
}
],
},
{
"rfqBpId": 1056637,
"Contact": [
{
"ID": 1019875,
"SelectedContact": true
}
],
}
],
script:
$scope.SelectedContacts = function() { //function starts by click on checkbox in html
let selectedContactList = [];
let finalList = [];
$scope.Context.Output = [];
for (let i = 0; i <= $scope.Context.rfqBp.length; i++) {
for (let j = 0; j <= $scope.Context.rfqBp[i].Contact.length; j++) {
if ($scope.Context.rfqBp[i].Contact[j].SelectedContact === true) {
selectedContactList = {
"ID": $scope.Context.rfqBp[i].Contact[j].ID
};
finalList.push(selectedContactList);
} else if ($scope.Context.rfqBp[i].Contact[j].SelectedContact !== true) {
continue;
}
$scope.Context.Output = finalList; //Output works but just for rfqBp[1]
};
};
$scope.Context.Output = finalList; //this part never reached
};
Output:
"Output": [
{
"ID": 1000014
},
{
"ID": 1016727
},
{
"ID": 1017452
}
]
I try to get following:
"Output": [
{
"ID": 1000014
},
{
"ID": 1016727
},
{
"ID": 1017452
},
{
"ID": 1016236
},
{
"ID": 1019563
},
{
"ID": 1019875
}
]
You can use Array.prototype.flatMap() combined with Array.prototype.filter(), Array.prototype.map() and Destructuring assignment:
const rfqBp = [{rfqBpId: 1041650,Contact: [{ID: 1000014,SelectedContact: true,},{ID: 1002411,SelectedContact: true,},{ID: 1016727,SelectedContact: true,},{ID: 1017452,SelectedContact: true,},],},{rfqBpId: 1052326,Contact: [{ID: 1016236,SelectedContact: true,},{ID: 1019563,SelectedContact: true,},],},{rfqBpId: 1056632,Contact: [{ID: -1,SelectedContact: false,},],},{rfqBpId: 1056637,Contact: [{ID: 1019875,SelectedContact: true,},],},]
const result = rfqBp
.flatMap(({ Contact }) => Contact
.filter(({ ID }) => ID > 0) // Filter to exclude negative `ID`s
.map(({ ID }) => ({ ID }))
)
console.log(result)

Comparing an array with another array plus adding a counter

So I'm reformatting my data and I noticed that my data isn't quite getting restructured the way I want it to. I noticed that my results come back as
[
{
"name": "sites",
"parent": null,
"count": 3
},
{
"name": "group1",
"parent": "sites",
"count": 3
},
{
"name": "bk",
"parent": "group1",
"count": 3
},
{
"name": "sitepages",
"parent": "bk",
"count": 1
},
{
"name": "home.aspx",
"parent": "sitepages",
"count": 1
}
]
It isn't grabbing my "not matches". I've spent so much time looking it over and I'm coming to a blank. It should be
[
{
"name": "sites",
"parent": null,
"count": 3
},
{
"name": "group1",
"parent": "sites",
"count": 3
},
{
"name": "bk",
"parent": "group1",
"count": 3
},
{
"name": "sitepages",
"parent": "bk",
"count": 1
},
{
"name": "home.aspx",
"parent": "sitepages",
"count": 1
},
{
"name": "tester",
"parent": "bk",
"count": 1
},
{
"name": "tester",
"parent": "home.aspx",
"count": 1
},
{
"name": "_layouts",
"parent": "bk",
"count": 1
},
{
"name": "15",
"parent": "_layouts",
"count": 1
},
{
"name": "upload.aspx",
"parent": "15",
"count": 1
},
]
I believe something is missing in my loop.
var testArr = [
{
Author: { Title: "Mitchell" },
BrowserType: "FireFox",
Created: "2017-04-25T16:39:40Z",
pathname: "sites/group1/bk/sitepages/home.aspx"
},
{
Author: { Title: "Pierre" },
BrowserType: "Opera",
Created: "2017-04-25T16:39:40Z",
pathname: "sites/group1/bk/tester/home.aspx"
},
{
Author: { Title: "Mizell" },
BrowserType: "IE",
Created: "2017-04-25T16:47:02Z",
pathname: "sites/group1/bk/_layouts/15/upload.aspx"
}
];
function reduceData(data) {
var root = null;
var newArr = null;
var itemContainer = [];
var masterArr = [];
var filteredArr = [];
data.forEach(function (props, idx) {
//check the last character of the pathname is "/" and removes it
if (props.pathname.charAt(props.pathname.length - 1) === "/") {
props.pathname = props.pathname.substring(0, props.pathname.length - 1);
}
//lowercase the pathname + split into strings
props.pathname = props.pathname.toLowerCase().split("/");
//format the pathname
var lastItem = "";
newArr = props.pathname.reduce(function (acc, props, index) {
if (acc.length === 0) {
acc.push({ name: props, parent: null, count: 1 });
lastItem = props;
} else {
acc.push({ name: props, parent: lastItem, count: 1 });
lastItem = props;
}
return acc;
}, []);
//The first iteration
if (idx === 0) {
itemContainer = newArr;
} else {
for (var i = 0; i < itemContainer.length; i++) {
// Loop for newArr
for (var j = 0; j < newArr.length; j++) {
//compare the element of each and every element from both of the arrays
//console.log(masterArr[i], newArr[j]);
if (
itemContainer[i].name === newArr[j].name &&
itemContainer[i].parent === newArr[j].parent
) {
//Match
masterArr[i] = {
name: itemContainer[i].name,
parent: itemContainer[i].parent,
count: itemContainer[i].count++
};
} else {
//Doesn't Match
masterArr[i] = {
name: itemContainer[i].name,
parent: itemContainer[i].parent,
count: itemContainer[i].count
};
}
}
}
}
});
console.log(masterArr)
}
reduceData(testArr)
ok.. I revamp your code a little..
delete the if else after the //The first iteration, and use this instead..
newArr.forEach((newEl) => {
const matchIdx = masterArr.findIndex((masterEl) => masterEl.name === newEl.name && masterEl.parent === newEl.parent);
if(matchIdx < 0){
masterArr.push(newEl);
}
else {
masterArr[matchIdx].count = masterArr[matchIdx].count + 1;
}
});

Normalize JSON to a custom schema

I have an array of objects with the following format
var arr = [
{
"productId": "123456",
"productName": "Test Product 1",
"description": [
"This is delicious",
"Suitable for vegetarian"
],
"attributes": {
"internalId": "091283"
"category": "Dairy"
},
"order": 1
}
];
And I am trying to map into something like below
[
[{
{
"name": "productId",
"value": "123456"
},
{
"name": "productName",
"value": "Test Product 1"
},
{
"name": "description",
"value": ["This is delicious", "Suitable for vegetarian"]
},
{
"name": "attributes",
"value": {
{
"name": "internalId",
"value": "091283"
},
{
"name": "category",
"value": "Dairy"
}
}
},
{
"name": "order",
"value": 1
}
}]
]
I tried mapping simple properties before going further and now stuck at getting only the last property of each object in the loop.
Suppose I don't know what are the format of incoming data and how can I normalize the JSON object to the format I want?
normalizeJson = (array) => {
for(i = 0; i < array.length; i++){
normalizedJson[i] = {};
Object.keys(array[i]).forEach(key => {
if (array[i][key] && typeof array[i][key] === "object") {
// normalizeJson(obj[key]);
// console.log(key + ' is object');
return;
} else {
o = {};
o["name"] = key;
o["value"] = array[i][key];
normalizedJson[i] = o;
// normalizedJson[i]["name"] = key;
// normalizedJson[i].value = array[i][key];
// console.log(key);
return;
}
});
}
console.log(normalizedJson);
};
Or is there any library I can use in order to achieve this?
Try this
var obj = [
{
productId: "123456",
productName: "Test Product 1",
description: ["This is delicious", "Suitable for vegetarian"],
attributes: {
internalId: "091283",
category: "Dairy",
},
order: 1,
},
];
function normalizeObject(obj) {
var result = [];
if (Array.isArray(obj)) {
for (let i of obj) {
result.push(normalizeObject(i));
}
} else if (typeof obj == "object") {
for (let i of Object.keys(obj)) {
result.push({ name: i, value: normalizeObject(obj[i]) });
}
} else {
return obj;
}
return result;
}
console.log(JSON.stringify(normalizeObject(obj), null, 2));
This looping method called recursion. Which is loop by calling function itself.

Can I remove a redundant data structure wrapper from my nested array to nested json script?

I wrote this script to convert a nested array with the structure below to a nested object with parent child relationships.
list = [
['lvl-1 item-1', 'lvl-2 item-1'],
['lvl-1 item-1', 'lvl-2 item-1', 'lvl-3 item-1'],
['lvl-1 item-1', 'lvl-2 item-1', 'lvl-3 item-2'],
['lvl-1 item-2', 'lvl-2 item-1', 'lvl-3 item-1'],
['lvl-1 item-2', 'lvl-2 item-2', 'lvl-3 item-2', 'lvl-4 item-1'],
];
It seems to do the trick, but in order to prime the script I've had to add data.children wrapper around the initial data structure. I'm not convinced it is needed, though I haven't been able to workout how to get rid of it.
Can anyone see anything I'm missing?
console.log(nestedArrayToJson(list));
function nestedArrayToJson(structure) {
const top_item = '0';
// This was added to behave like the child data structure.
let data = {
children: [
{
name: top_item,
parent: null,
children: [],
}],
};
for(let i = 0; i < structure.length; i++) {
let parents = [top_item];
for(let j = 0; j < structure[i].length; j++) {
let obj = data;
for(parent of parents) {
obj = obj.children.find(o => o.name === parent);
}
const name = structure[i][j];
if(!obj.children.find(o => o.name === name)) {
obj.children.push({
name,
parent,
children: [],
});
}
parents.push(structure[i][j]);
}
}
return data.children[0];
}
Sample Output
{
"name": "0",
"parent": null,
"children": [
{
"name": "lvl-1 item-1",
"parent": "0",
"children": [
{
"name": "lvl-2 item-1",
"parent": "lvl-1 item-1",
"children": [
{
"name": "lvl-3 item-1",
"parent": "lvl-2 item-1",
"children": []
},
{
"name": "lvl-3 item-2",
"parent": "lvl-2 item-1",
"children": []
}
]
}
]
},
{
"name": "lvl-1 item-2",
"parent": "0",
"children": [
{
"name": "lvl-2 item-1",
"parent": "lvl-1 item-2",
"children": [
{
"name": "lvl-3 item-1",
"parent": "lvl-2 item-1",
"children": []
}
]
},
{
"name": "lvl-2 item-2",
"parent": "lvl-1 item-2",
"children": [
{
"name": "lvl-3 item-2",
"parent": "lvl-2 item-2",
"children": [
{
"name": "lvl-4 item-1",
"parent": "lvl-3 item-2",
"children": []
}
]
}
]
}
]
}
]
}
The for loops can be cleaned up by extracting some functionality to named functions.
const node = (name, parent = null) => ({name, parent, children: []}) handles creating a node.
Nodes can then be added with addNode()
To search for the current next parent node findNamedNode()
If a node with the current name is found it moves down to the next node. If no node exists with the current name it is created.
function createTree(arr, topItem = 'Top') {
const node = (name, parent = null) => ({name, parent, children: []});
const addNode = (parent, child) => {
parent.children.push(child);
return child;
};
const findNamedNode = (name, parent) => {
for(const child of parent.children) {
if(child.name === name) { return child; }
const found = findNamedNode(name, child);
if(found) { return found; }
}
};
const top = node(topItem);
let current;
for(const children of arr) {
current = top;
for(const name of children) {
const found = findNamedNode(name, current);
current = found ? found : addNode(current,
node(name, current.name));
}
}
return top;
}
Thanks to the help from #Blindman67 on Code Review.
https://codereview.stackexchange.com/questions/219418/convert-nested-array-of-values-to-a-tree-structure/

Categories