Convert flat data to 3 level hierarchy/tree - javascript

I'm currently converting my flat data to a hierarchy with 2 levels, but I now need to add an additional 3rd level to this data. I haven't been able to figure out how to modify by existing method so far. I'm open to completely new methods also.
Here is the code I'm currently using to do the conversion:
chartdata = sfdata.data.reduce((acc, {
items: [cat, val, salesTY, salesLY, unitsTY, unitsLY]
}) => {
acc[cat] = acc[cat] || [];
acc[cat].push({
name: val,
salesTY: salesTY,
salesLY: salesLY,
unitsTY: unitsTY,
unitsLY: unitsLY
});
return acc;
}, {});
// Generate desired output structure.
chartdata = Object.entries(chartdata).map(([k, v]) => ({
category: k,
children: v
}));
It maps to the different categories and then converts that map to a category>children structure
And here is the fiddle where I'm using this tree structure: http://jsfiddle.net/zt4nhxcw/3/
I've started a new fiddle here with the new Brand data included: http://jsfiddle.net/t1uz85b2/
The goal is to add a 3rd level. So every child at the 2nd level would have Brand underneath of them.
Here is a very small snippet of how the data comes in:
[
{
"items": [
"SSD",
"PBNA",
"MOUNTAIN DEW",
851255.3500000001,
672407.8399999997,
782364.9999999991,
641579.0000000006
],
"hints": {
"index": 0
}
},
{
"items": [
"Energy",
"RED BULL NORTH AMERICA",
"RED BULL",
836632.2299999997,
654021.2899999995,
267216,
214321.00000000015
],
"hints": {
"index": 1
}
},
{
"items": [
"SSD",
"PBNA",
"PEPSI",
478704.02999999974,
392746.69999999995,
533557.0000000006,
457008.0000000001
],
"hints": {
"index": 4
}
},
{
"items": [
"Energy",
"RED BULL NORTH AMERICA",
"RED BULL EDITIONS",
449618.55000000016,
328150.8999999997,
162428.9999999999,
117521.00000000001
],
"hints": {
"index": 5
}
},
{
"items": [
"SSD",
"CCNA",
"COKE",
349685.7899999996,
276766.95,
445485.0000000002,
351214.0000000003
],
"hints": {
"index": 9
}
}
]
And here is the final structure I'm trying to achieve:
[
{
"category": "SSD",
"children": [
{
"brand": "PBNA",
"children": [
{
"name": "MOUNTAIN DEW",
"salesTY": 851255.3500000001,
"salesLY": 672407.8399999997,
"unitsTY": 782364.9999999991,
"unitsLY": 641579.0000000006
}
]
},
{
"brand": "CCNA",
"children": [
{
"name": "COKE",
"salesTY": 349685.7899999996,
"salesLY": 276766.95,
"unitsTY": 445485.0000000002,
"unitsLY": 351214.0000000003
}
]
}
]
},
{
"category": "Energy",
"children": [
{
"brand": "RED BULL NORTH AMERICA",
"children": [
{
"name": "RED BULL",
"salesTY": 836632.2299999997,
"salesLY": 654021.2899999995,
"unitsTY": 267216,
"unitsLY": 214321.00000000015
},
{
"name": "RED BULL EDITIONS",
"salesTY": 449618.55000000016,
"salesLY": 328150.8999999997,
"unitsTY": 162428.9999999999,
"unitsLY": 117521.00000000001
}
]
}
]
}
]

You could take a dynamic approach and take an array with the keys for all values and a limit of the wanted depth.
const
data = [{ items: ["SSD", "PBNA", "MOUNTAIN DEW", 851255.3500000001, 672407.8399999997, 782364.9999999991, 641579.0000000006], hints: { index: 0 } }, { items: ["Energy", "RED BULL NORTH AMERICA", "RED BULL", 836632.2299999997, 654021.2899999995, 267216, 214321.00000000015], hints: { index: 1 } }, { items: ["SSD", "PBNA", "PEPSI", 478704.02999999974, 392746.69999999995, 533557.0000000006, 457008.0000000001], hints: { index: 4 } }, { items: ["Energy", "RED BULL NORTH AMERICA", "RED BULL EDITIONS", 449618.55000000016, 328150.8999999997, 162428.9999999999, 117521.00000000001], hints: { index: 5 } }, { items: ["SSD", "CCNA", "COKE", 349685.7899999996, 276766.95, 445485.0000000002, 351214.0000000003], hints: { index: 9 } }],
keys = ['category', 'brand', 'name', 'salesTY', 'salesLY', 'unitsTY', 'unitsLY'],
limit = 2,
result = data
.reduce((temp, { items }) => {
keys
.slice(0, limit)
.reduce(function (r, k, i) {
if (!r[items[i]]) {
r[items[i]] = { _: [] };
r._.push({ [k]: items[i], children: r[items[i]]._ });
}
return r[items[i]];
}, temp)
._
.push(keys
.slice(limit)
.reduce((o, k, i) => (o[k] = items[i + limit], o), {})
);
return temp;
}, { _: [] })
._;
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Related

How to filtering out the multiple nested object in Javascript object

Javascript
I have a nested array of objects, I'm trying to filter the given array of objects using a property from the third level of its array property value. For example, from the below array I like to filter the entire array using the property ListId: 10
Example
let test = {
"test":true,
"group":[
{
"name":"header",
"value":[
{
"id":"0",
"list":[
{
"ListId":10,
"name":"string1",
"state":"BY",
"techId":0
},
{
"ListId":11,
"name":"string2",
"state":"BY"
},
{
"ListId":12,
"name":"string3",
"state":"BY"
}
]
}
]
},
{
"name":"header2",
"value":[
{
"id":"01",
"list":[
{
"ListId":100,
"name":"string1",
"state":"BY",
"techId":0
},
{
"ListId":111,
"name":"string2",
"state":"BY"
},
{
"ListId":121,
"name":"string3",
"state":"BY"
}
]
}
]
}
]
}
Filtervalue with ListId = 10
Expected output :
{
"test":true,
"group":[
{
"name":"header",
"value":[
{
"id":"0",
"list":[
{
"ListId":10,
"name":"string1",
"state":"BY",
"techId":0
}
]
}
]
}
]
}
How can I use the filter method using javascript to get this expected result?
You can two it in two times :
First, filter the list arrays,
Secondly filter the groups array using the some method
let test= {
"test": true,
"group": [
{
"name": "header",
"value": [
{
"id": "0",
"list": [
{
"ListId": 10,
"name": "string1",
"state": "BY",
"techId": 0
},
{
"ListId": 11,
"name": "string2",
"state": "BY"
},
{
"ListId": 12,
"name": "string3",
"state": "BY"
}
]
}
]
},
{
"name": "header2",
"value": [
{
"id": "01",
"list": [
{
"ListId": 100,
"name": "string1",
"state": "BY",
"techId": 0
},
{
"ListId": 111,
"name": "string2",
"state": "BY"
},
{
"ListId": 121,
"name": "string3",
"state": "BY"
}
]
}
]
}
]
}
test.group.forEach(group => {
group.value.forEach(value => {
value.list = value.list.filter(list => list.ListId === 10)
})
})
test.group = test.group.filter(group => group.value.some(value => value.list.length > 0))
console.log(test)
Note : You should use plural names for you arrays, it helps understanding the data. For example lists not list for the array.
let z ={"group1": [
{
"name": "header",
"value": [
{
"id": 0,
"list": [
{
"ListId": 10,
"Name": "string1"
},
{
"ListId": 11,
"Name": "string2"
}
]
}
]
}
]}
// This function was written from understading that 'group1' is not a fixed property, but part of a dynamic list due to the number '1'
const getItemByListId = (list, listId) => {
const listKeys = Object.keys(list);
const selectedListKey = listKeys.find(key => {
const groupItems = list[key];
const selectedItem = groupItems.find(({ value: nestedItems }) => {
const selectedNestedItem = nestedItems.find(({ list }) => {
const selectedList = list.find(({ ListId }) => ListId === listId)
return selectedList;
});
return selectedNestedItem;
});
return selectedItem;
});
if (!selectedListKey) {
return null;
}
return list[selectedListKey];
};
console.log(getItemByListId(z, 10));

How to group array objects into sub objects and count them

I receive an array from an api which looks like this
input = [
{ choices:[
{"food":"breakfast","preference":"tea"},
{"food":"lunch","preference":"burger"},
{"food":"supper","preference":"rice"}
]
},
{ choices:[
{"food":"breakfast","preference":"coffee"},
{"food":"lunch","preference":"burger"},
{"food":"supper","preference":"yam"}
]
},
{ choices:[
{"food":"breakfast","preference":"tea"},
{"food":"lunch","preference":"bread"},
{"food":"supper","preference":"yam"}
]
},
{ choices:[
{"food":"breakfast","preference":"coffee"},
{"food":"lunch","preference":"bread"},
{"food":"supper","preference":"rice"}
]
}
]
I tried Group by the array of objects based on the property and also the count in javascript
I need to group the individual preferences and count them
groupedChoices = [
[
{ "preference": "tea", "count": 3 },
{ "preference": "coffee", "count": 2 }
],
[
{ "preference": "burger", "count": 3 },
{ "preference": "bread", "count": 2 }
],
[
{ "preference": "rice", "count": 3 },
{ "preference": "yam", "count": 2 }
]
]
const input = [
{ choices:[
{"food":"breakfast","preference":"tea"},
{"food":"lunch","preference":"burger"},
{"food":"supper","preference":"rice"}
]
},
{ choices:[
{"food":"breakfast","preference":"coffee"},
{"food":"lunch","preference":"burger"},
{"food":"supper","preference":"yam"}
]
},
{choices: [
{"food":"breakfast","preference":"tea"},
{"food":"lunch","preference":"bread"},
{"food":"supper","preference":"yam"}
]
},
{ choices:[
{"food":"breakfast","preference":"coffee"},
{"food":"lunch","preference":"bread"},
{"food":"supper","preference":"rice"}
]
}
];
const result = input.reduce((acc, { choices }) => {
choices.forEach(({ food, preference }) => {
if (!acc[food]) acc[food] = {}
if (!acc[food][preference]) acc[food][preference] = 1
else ++acc[food][preference]
})
return acc
}, {})
console.log(result)

Grouping array nested value while comparing 2 objects

I am working in small react project & I am facing issue in grouping the data. Requirement is to group the id & its feature into a single row if same id is there in before & after object.
Json Data:
{
"before":{
"device": [
{
id:"1234",
price:"10,
features:[
{name:"samsung",price:"10"},
{name:"Apple",price:"20"}
]
},
{id:"2154",
price:"20,
features:[
{name:"samsung",price:"30"},
{name:"Moto",price:"40"}
]
]
},
"after":{
"device": [
{
id:"1234",
price:"50,
features:[
{name:"samsung",price:"20"},
{name:"Lenovo",price:"30"}
]
},
{id:"2158",
price:"40,
features:[
{name:"samsung",price:"30"}
]
]
}
}
Expected grouping to be shown in UI is shared in image.
I tried to get unique ids in one array and lopping through after array and comparing unique array id I am getting unique id to show but issue i am facing while grouping their related feature.
Can anyone please help me to get a best approach to handle this requirement.
Thanks
There are 3 things i'd suggest you:
1.) Please verify the data your'e posting is correct and in proper format, people won't be able to help if the data is incorrect.
2.) The UI display requirement should be simple enough.
Now, if you still want to achieve this requirement i believe the correct JSON and the merged output json will look something like below:
//Correct input data that you have:
var input = {
"before": {
"device": [
{
"id": "1234",
"price": "10",
"features": [
{
"name": "samsung",
"price": "10"
},
{
"name": "Apple",
"price": "20"
}
]
},
{
"id": "2154",
"price": "20",
"features": [
{
"name": "samsung",
"price": "30"
},
{
"name": "Moto",
"price": "40"
}
]
}
]
},
"after": {
"device": [
{
"id": "1234",
"price": "50",
"features": [
{
"name": "samsung",
"price": "20"
},
{
"name": "Lenovo",
"price": "30"
}
]
},
{
"id": "2158",
"price": "40",
"features": [
{
"name": "samsung",
"price": "30"
}
]
}
]
}
};
// Output JSON which you should need to show the desired output.
var output = {
"devices": [
{
"id": 1234,
"feature": [
{
"name": "1234",
"price": {
"before": 10,
"after": 50
}
},
{
"name": "samsung",
"price": {
"before": 10,
"after": 20
}
},
{
"name": "apple",
"price": {
"before": 10,
"after": 0
}
},
{
"name": "lenovo",
"price": {
"before": 0,
"after": 30
}
}
]
}
]
};
3.) Please try to get the desired output from input yourself as this will help you learn a lot of things in between, as suggested by some please use map, filter, forEach for your requirement.
Hope this helps. Thanks!
You could take a nested approach for grouping.
var data = { before: { device: [{ id: "1234", price: "10", features: [{ name: "samsung", price: "10" }, { name: "Apple", price: "20" }] }, { id: "2154", price: "20", features: [{ name: "samsung", price: "30" }, { name: "Moto", price: "40" }] }] }, after: { device: [{ id: "1234", price: "50", features: [{ name: "samsung", price: "20" }, { name: "Lenovo", price: "30" }] }, { id: "2158", price: "40", features: [{ name: "samsung", price: "30" }] }] } },
cols = Object.fromEntries(Object.keys(data).map(k => [k, 0])),
result = Object.values(Object.entries(data).reduce((r, [col, { device }]) => {
device.forEach(({ id, price, features }) => {
r[id] = r[id] || [{ id, ...cols }];
r[id][0][col] = price;
features.forEach(({ name, price }) => {
let temp = r[id].find(q => q.name === name);
if (!temp) r[id].push(temp = { name, ...cols });
temp[col] = price;
});
});
return r;
}, {}));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can use lodash library for grouping
https://lodash.com/docs/3.10.1#groupBy
Comparing 2 objects, and output equivalent values
var has = {"before":[{
name: 'Nokia',
os: 'Andriod',
features: {
camera: "200 MPixel Camera",
battery: "24 hours battery backup",
}
}],
"after":[{
name: 'Samsung',
os: 'Andriod',
features: {
camera: "200 MPixel Camera",
battery: "30 hours battery backup",
}
}]
};
function compare(Pro1, Pro2) {
var Val1 = Object.values(Pro1);
var Val2 = Object.values(Pro2);
var equivalent = [];
var keys = Object.keys(Pro1);
keys.forEach(k => {
if (Pro1.hasOwnProperty(k) && Pro2.hasOwnProperty(k)) {
if (typeof Pro1[k] === 'object') {
let recursiveResult = compare(Pro1[k], Pro2[k]);
equivalent.push(...recursiveResult);
} else if (Pro1[k] === Pro2[k]) {
equivalent.push(Pro1[k]);
}
}
});
return equivalent;
}
let equiv = compare(has["before"], has["after"]);
console.log(equiv);

Object transformation using a functional approach

I am reading a simple data set from a data.txt file. I would like to take this data and transform it into a specific object as per my example below. I have managed to get it into a somewhat usable JSON object but this is not ideal. I have included an example of the desired object.
Here is my app.js file:
let output = fs.readFileSync('./data.txt', 'UTF8')
.trim()
.split('\r\n')
.map((line) => line.split(';'))
.reduce((customers, line) => {
customers.push({
name: line[0],
product: [{
item: line[1],
serial: line[2],
year: line[3]
}]
})
return customers
}, [])
console.log(JSON.stringify(output, null, 2))
This currently the above NodeJs code returns the following array object:
[
{
"name": "Nancy",
"product": [
{
"item": "Macbook Pro",
"serial": "A34D05980FCD4303",
"year": "2019"
}
]
},
{
"name": "Nancy",
"product": [
{
"item": "iPad",
"serial": "O0403X3028423C92",
"year": "2015"
}
]
},
{
"name": "Nancy",
"product": [
{
"item": "iPhone",
"serial": "X3830238S3309230",
"year": "2017"
}
]
},
{
"name": "John",
"product": [
{
"item": "Macbook Pro",
"serial": "X2020J393983H380",
"year": "2013"
}
]
},
{
"name": "John",
"product": [
{
"item": "iPhone",
"serial": "X38320093X032309",
"year": "2015"
}
]
},
{
"name": "fluffikins",
"product": [
{
"item": "iMac",
"serial": "F392D392033X3232",
"year": "2013"
}
]
},
{
"name": "fluffikins",
"product": [
{
"item": "iPad",
"serial": "FE322230D3223S21",
"year": "2011"
}
]
}
]
What I am trying to do is get the below object returned - ideally still following the same functional approach:
[
{
"name": "Nancy",
"product": [
{
"item": "Macbook Pro",
"serial": "A34D05980FCD4303",
"year": "2019"
},
{
"item": "iPad",
"serial": "O0403X3028423C92",
"year": "2015"
},
{
"item": "iPhone",
"serial": "X3830238S3309230",
"year": "2017"
}
]
},
{
"name": "John",
"product": [
{
"item": "Macbook Pro",
"serial": "X2020J393983H380",
"year": "2013"
},
{
"item": "iPhone",
"serial": "X38320093X032309",
"year": "2015"
}
]
},
{
"name": "fluffikins",
"product": [
{
"item": "iMac",
"serial": "F392D392033X3232",
"year": "2013"
},
{
"item": "iPad",
"serial": "FE322230D3223S21",
"year": "2011"
}
]
}
]
Here is my mock data set that lives in data.txt
Nancy;Macbook Pro;A34D05980FCD4303;2019
Nancy;iPad;O0403X3028423C92;2015
Nancy;iPhone;X3830238S3309230;2017
John;Macbook Pro;X2020J393983H380;2013
John;iPhone;X38320093X032309;2015
fluffikins;iMac;F392D392033X3232;2013
fluffikins;iPad;FE322230D3223S21;2011
Instead of an array you can use Map in reduce as accumulator, use name as key in Map and club value of all keys, finally just get the values Map to get desired output
const data = `Nancy;Macbook Pro;A34D05980FCD4303;2019
Nancy;iPad;O0403X3028423C92;2015
Nancy;iPhone;X3830238S3309230;2017
John;Macbook Pro;X2020J393983H380;2013
John;iPhone;X38320093X032309;2015
fluffikins;iMac;F392D392033X3232;2013
fluffikins;iPad;FE322230D3223S21;2011`
const final = data.split('\n')
.map(v => v.split(';'))
.reduce((op, [name, item, serial, year]) => {
let obj = { item, serial, year }
if (op.has(name)) {
op.get(name).products.push(obj)
} else{
op.set(name,{name, products:[obj]})
}
return op
}, new Map())
console.log([...final.values()])
Here is a "functional version" that utilizes a Map to find duplicates in O(1):
(map => (
fs.readFileSync('./data.txt', 'UTF8')
.trim()
.split('\r\n')
.map((line) => line.split(';'))
.forEach(([name, item, serial, year]) =>
map.has(name)
? map.get(name).product.push({ item, serial, year })
: map.set(name, { name, product: [{ item, serial, year }] })
),
[...map.values()]
)(new Map)
But seriously, whats so bad about imperative style?:
const customers = new Map;
const entries = fs.readFileSync('./data.txt', 'UTF8')
.trim()
.split('\r\n');
for(const entry of entries) {
const [name, item, serial, year] = entry.split(";");
const product = { item, serial, year };
if(customers.has(name)) {
customers.get(name).product.push(product);
} else customers.set(name, { name, product: [product] });
}
const result = [...customers.values()];
You can modify the .reduce function to only add a new item to the array if there isn't one with that name. If there is, just add the product to that item's product array.
const data = `Nancy;Macbook Pro;A34D05980FCD4303;2019
Nancy;iPad;O0403X3028423C92;2015
Nancy;iPhone;X3830238S3309230;2017
John;Macbook Pro;X2020J393983H380;2013
John;iPhone;X38320093X032309;2015
fluffikins;iMac;F392D392033X3232;2013
fluffikins;iPad;FE322230D3223S21;2011`;
const result = data.trim()
.split('\n')
.map((line) => line.split(';'))
.reduce((customers, line) => {
const product = {
item: line[1],
serial: line[2],
year: line[3]
};
const customer = customers.find(({
name
}) => name === line[0]);
if (customer) {
customer.product.push(product);
} else {
customers.push({
name: line[0],
product: [product]
});
}
return customers
}, []);
console.log(result);

Objects with Array values into a tree

I have an array of objects where every object has an array of values. The data structure is not ideal but it's the only way I can access it. I'm trying to turn this structure into a tree structure so I can build a D3 indented collapsible table.
I've tried to modify some previous answers I've found but have yet to be successful. Here is a link to the current JSFiddle I've been working on.
http://jsfiddle.net/COLTstreet/fsve7w2L/25/
This is a small example of how the data comes to me.
{
"data": [
{
"items": [
"All Other",
"4C FOODS CORP"
],
"hints": {
"index": 0
}
},
{
"items": [
"All Other",
"PBNA"
],
"hints": {
"index": 14
}
},
{
"items": [
"All Other",
"PRIVATE LABEL"
],
"hints": {
"index": 15
}
},
{
"items": [
"Base Water",
"CCNA"
],
"hints": {
"index": 18
}
},
{
"items": [
"Base Water",
"CRYSTAL GEYSER"
],
"hints": {
"index": 19
}
}
]
}
I need the code to finish like this:
[
{
"Category": "All Other",
"children": [
{
"name": "4C FOODS CORP"
},
{
"name": "PBNA"
},
{
"name": "PRIVATE LABEL"
}
]
},
{
"Category": "Base Water",
"children": [
{
"name": "CCNA"
},
{
"name": "CRYSTAL GEYSER"
}
]
}
]
One solution is to first use Array.reduce() in conjunction with destructuring to generate an object for group the elements by the category, and in a second step use Array.map() over the generated object entries to get the desired output:
const input = {
"data": [
{"items": ["All Other", "4C FOODS CORP"], "hints": {"index": 0}},
{"items": ["All Other", "PBNA"], "hints": {"index": 14}},
{"items": ["All Other", "PRIVATE LABEL"], "hints": {"index": 15}},
{"items": ["Base Water", "CCNA"], "hints": {"index": 18}},
{"items": ["Base Water", "CRYSTAL GEYSER"], "hints": {"index": 19}}
]
};
// Group by the category.
let res = input.data.reduce((acc, {items: [cat, val]}) =>
{
acc[cat] = acc[cat] || [];
acc[cat].push({name: val});
return acc;
}, {});
// Generate desired output structure.
res = Object.entries(res).map(([k, v]) => ({category: k, children: v}));
console.log(res);
.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}

Categories