Hierarchy grouping on JSON object using reduce - javascript

Currently I have an array of object which needs to be converted into hierarchy based on there key values. I am using reduce to achieve this , I was able to group on the 1st level of key (AREA) but I want it to happen recursively for other fields too and end result should be an hierarchy which looks like bellow , my hierarchy is huge but for example here I am taking 3 levels
Bellow is my array of object JSON
[{
"AREA": "EMEA",
"SUPER_REGION": "West Mediterranean Region",
"STATE": "Portugal",
},
{
"AREA": "USAC",
"SUPER_REGION": "United States",
"STATE": "california",
},
{
"AREA": "USAC",
"SUPER_REGION": "United States",
"STATE": "Texas",
},
{
"AREA": "ASIA",
"SUPER_REGION": "Japan",
"STATE": "Japan",
},
{
"AREA": "EMEA",
"SUPER_REGION": "North Europe Region",
"STATE": "ECOE",
},
{
"AREA": "USAC",
"SUPER_REGION": "United States",
"STATE": "Georgia",
}]
Expected result
[{
"EMEA": {
"West Mediterranean Region": {
"Portugal": null
},
"North Europe Region": {
"ECOE": null
}
}
},
{
"USAC": {
"United States": {
"Georgia": null
},
"United States": {
"Texas": null
},
"United States": {
"california": null
}
}
},
{
"ASIA": {
"Japan": {
"Japan": null
}
}
}]
code I have tried so far :
const result = data.reduce((grouped_Data, hierarchy) => {
const region = hierarchy["AREA"];
if (grouped_Data[region] == null) grouped_Data[region] = [];
grouped_Data[region].push(hierarchy);
return grouped_Data;
}, {});

You could reduce with each AREA as the key in the accumulator and group at each level
const input = [{AREA:"EMEA",SUPER_REGION:"West Mediterranean Region",STATE:"Portugal"},{AREA:"USAC",SUPER_REGION:"United States",STATE:"california"},{AREA:"USAC",SUPER_REGION:"United States",STATE:"Texas"},{AREA:"ASIA",SUPER_REGION:"Japan",STATE:"Japan"},{AREA:"EMEA",SUPER_REGION:"North Europe Region",STATE:"ECOE"},{AREA:"USAC",SUPER_REGION:"United States",STATE:"Georgia"}];
const grouped = input.reduce((acc, { AREA, SUPER_REGION, STATE }) => {
acc[AREA] ??= { [AREA]: {} }
acc[AREA][AREA][SUPER_REGION] ??= { }
acc[AREA][AREA][SUPER_REGION][STATE] = null
return acc;
}, {})
console.log(
Object.values(grouped)
)

Related

Group an array of objects based on multi keys in Javascript?

I would like to share with you an existence problem
I have array as follows
const data = [
{
"countries": ["France", "USA", "Canada"],
"city": "Paris",
"stadium": "Parce de france"
},
{
"countries": ["France", "Itlay", "Spain"],
"city": "Roma",
"arena": "La cigal"
},
{
"countries": ["France"],
"city": "Roma",
"stadium": "Olymico"
},
{
"countries": ["France"],
"city": "Paris",
"arena": "Velodrome"
},
]
I want to group it first by city then by stadium and arena if exist
How can I achieve this
output like :
{
"city": "Paris",
"arena": [
{
"value":"Velodrome",
"countries": ["France"]
}
],
"stadium": [
{
"value":"Parce de france",
"countries": ["France", "USA", "Canada"]}
]
}
{
"city": "Roma",
"arena": [
{
"value":"La cigal",
"countries": ["France", "Itlay", "Spain"]
}
],
"stadium": [
{
"value":"Olymico",
"countries": ["France"]
}
]
}
]
I was able to group by city with reduce
let group = function(array, key) {
return array.reduce(function(result, item) {
(result[item[key]] = result[item[key]] || []).push(item);
return result;
}, {});
};
How can I group the data first by city and then by stadium, including any arenas if they exist?
The required output format is a little strange, but you can indeed do it with a single reduce() statement:
const data = [{
"countries": ["France", "USA", "Canada"],
"city": "Paris",
"stadium": "Parce de france"
}, {
"countries": ["France", "Itlay", "Spain"],
"city": "Roma",
"arena": "La cigal"
}, {
"countries": ["France"],
"city": "Roma",
"stadium": "Olymico"
}, {
"countries": ["France"],
"city": "Paris",
"arena": "Velodrome"
}];
const result = Object.values(
data.reduce((a, {city, arena, stadium, countries}) => {
a[city] ??= { city, arena: [], stadium: [] };
if (arena) a[city].arena.push({ value: arena, countries });
if (stadium) a[city].stadium.push({ value: stadium, countries });
return a;
}, {})
);
console.log(result);
Try using loadash for such computations it will make it easy
https://lodash.com/docs/4.17.15#groupBy
HTML
<scriptsrc="https://cdn.jsdelivr.net/npm/lodash#4.17.21/lodash.min.js"></script>
js
console.log(_.groupBy(_.groupBy(_.groupBy(data,'arena'),'stadium'),'city'));
You can use map to transform each item in the data array into an object with the city, arena, and stadium. Later use reduce to group items by city and use spread operator to merge the arena and stadium data into arrays. In the end use Object.values() to get array of grouped objects:
const data = [
{
"countries": ["France", "USA", "Canada"],
"city": "Paris",
"stadium": "Parce de france"
},
{
"countries": ["France", "Itlay", "Spain"],
"city": "Roma",
"arena": "La cigal"
},
{
"countries": ["France"],
"city": "Roma",
"stadium": "Olymico"
},
{
"countries": ["France"],
"city": "Paris",
"arena": "Velodrome"
},
]
const groupByCity = (array) =>
Object.values(
array
.map(({ city, stadium, arena, countries }) => ({
city,
arena: arena ? [{ value: arena, countries }] : [],
stadium: stadium ? [{ value: stadium, countries }] : [],
}))
.reduce((result, { city, arena, stadium }) => {
result[city] = result[city] || { city, arena: [], stadium: [] };
result[city].arena = [...result[city].arena, ...arena];
result[city].stadium = [...result[city].stadium, ...stadium];
return result;
}, {})
);
const result = groupByCity(data);
console.log(result);

Transforming a JSON structure in javascript

I have data for a simple online store coming from a headless CMS. The data looks like this:
[
{
"id": 12312,
"storeName": "Store1",
"googleMapsUrl": "https://example1.com",
"country": "Australia",
"state": "New South Wales"
},
{
"id": 12313,
"storeName": "Store2",
"googleMapsUrl": "https://example2.com",
"country": "Australia",
"state": "Queensland"
},
{
"id": 12314,
"storeName": "Store3",
"googleMapsUrl": "https://example3.com",
"country": "Indonesia",
"state": "Java"
},
]
This data will be used to make a simple store locator sorted by 1) Country and 2) State.
I'm unable to change anything on the CMS. Looking for suggested way to loop through this flat data object and list by country and state.
EDIT: I created this to extract the unique countries:
let newArray = this.$page.stores.edges.map(item =>{
let newObj = {};
newObj[item.node.country] = item.node.country;
newObj[item.node.country.state] = item.node.state;
console.log(newObj)
return newObj;
});
But again from there not sure how I would connect that to states and ultimately stores.
Ordered by country and state.
var data = [{
"id": 12314,
"storeName": "Store3",
"googleMapsUrl": "https://example3.com",
"country": "Indonesia",
"state": "Java"
},
{
"id": 12315,
"storeName": "Store4",
"googleMapsUrl": "https://example4.com",
"country": "Australia",
"state": "Queensland"
},
{
"id": 12312,
"storeName": "Store1",
"googleMapsUrl": "https://example1.com",
"country": "Australia",
"state": "New South Wales"
},
{
"id": 12313,
"storeName": "Store2",
"googleMapsUrl": "https://example2.com",
"country": "Australia",
"state": "Queensland"
},
]
var stores = {};
var countries = new Set(data.map(item => item.country).sort());
countries.forEach(country => {
var states = {};
var items = data.filter(item => item.country == country);
new Set(items.map(item => item.state).sort()).forEach(state => {
states[state] = items.filter(item => item.state == state)
.sort(item => item.state);
});
stores[country] = states;
});
console.log(stores['Australia']['Queensland']);
You can build the following country-state map by reducing the stores.
{
"Australia": {
"states": [
"New South Wales",
"Queensland"
]
},
"Indonesia": {
"states": [
"Java"
]
}
}
I reversed the order of the store data so that the sorting will actually make a difference.
let stores = sortByCountryAndState(getData())
let locations = groupByCountryAndState(stores)
console.log(locations)
function sortByCountryAndState(data) {
return data.sort((storeA, storeB) => {
if (storeA == null) return 1
if (storeB == null) return -1
let diff = storeA.country.localeCompare(storeB.country)
if (diff === 0) return storeA.state.localeCompare(storeB.state)
return diff
})
}
function groupByCountryAndState(data) {
return data.reduce((countries, store, index) => {
let country = countries[store.country] || {}
return Object.assign(countries, {
[store.country] : Object.assign(country, {
states : pushAndSort(country.states || [], store.state)
})
})
}, {})
}
function pushAndSort(arr, value) {
if (arr.indexOf(value) === -1) arr.push(value)
//return arr.sort() // Sorting is done before the grouping...
return arr
}
function getData() {
return [{
"id": 12314,
"storeName": "Store3",
"googleMapsUrl": "https://example3.com",
"country": "Indonesia",
"state": "Java"
}, {
"id": 12313,
"storeName": "Store2",
"googleMapsUrl": "https://example2.com",
"country": "Australia",
"state": "Queensland"
}, {
"id": 12312,
"storeName": "Store1",
"googleMapsUrl": "https://example1.com",
"country": "Australia",
"state": "New South Wales"
} ]
}
.as-console-wrapper { top: 0; max-height: 100% !important; }

Reduce flat array to 2-level nested json object array

I have the following flat array:
{ "State": "New York", "Name": "Jane", "Product": "Apple" },
{ "State": "New York", "Name": "Jill", "Product": "Banana"},
{ "State": "California", "Name": "Jill", "Product": "Apple" },
{ "State": "California", "Name": "Jill", "Product": "Banana"}
Is it possible to create a 2-level nested array (i.e., Name > nested State Array > nested Product Array)? It would look like as follows:
{
"Name": "Jill",
"States": [
{
"State": "California",
"Products": [
{
"Product": "Apple"
},
{
"Product": "Banana"
}
]
},
{
"State": "New York",
"Products": [
{
"Product": "Banana"
}
]
}
]
},
{
"Name": "Jane",
"States": [
{
"State": "New York",
"Products": [
{
"Product": "Apple"
}
]
}
]
}
I have been able to get one level nested (States). How would you nest the second level?
Here is a stackblitz: https://stackblitz.com/edit/angular-lu6zj2
this.grouped_data = this.data.reduce((data, item) => {
data[item.Name] = data[item.Name] || { Name: item.Name, States: []}
data[item.Name].States.push(item)
return data;
}, {})
let data = [
{ "State": "New York", "Name": "Jane", "Product": "Apple" },
{ "State": "New York", "Name": "Jill", "Product": "Banana"},
{ "State": "California", "Name": "Jill", "Product": "Apple" },
{ "State": "California", "Name": "Jill", "Product": "Banana"}
];
let grouped = data.reduce((p, n) => {
// Create the Lady
if (!p[n.Name]) p[n.Name] = { States: [] };
// Check if the state exists, if not create it, then push product into it
if (!p[n.Name].States.some(state => state.State === n.State)) {
p[n.Name].States.push({ State: n.State, Products: [n.Product] });
} else {
!p[n.Name].States.find(state => state.State === n.State).Products.push(n.Product);
}
return p;
}, {});
console.log(grouped);
After that you can also remove duplicated products if you want to. I'll let you deal with it !
EDIT I didn't respect your model, what a dumbass am I ... Here it is :
let data = [
{ "State": "New York", "Name": "Jane", "Product": "Apple" },
{ "State": "New York", "Name": "Jill", "Product": "Banana"},
{ "State": "California", "Name": "Jill", "Product": "Apple" },
{ "State": "California", "Name": "Jill", "Product": "Banana"}
];
let grouped = data.reduce((p, n) => {
// Create the Lady
if (!p.some(lady => lady.Name === n.Name)) p.push({ Name: n.Name, States: [] });
let lady = p.find(lady => lady.Name === n.Name);
// Check if the state exists, if not create it, then push product into it
if (!lady.States.some(state => state.State === n.State)) {
lady.States.push({ State: n.State, Products: [n.Product] });
} else {
lady.States.find(state => state.State === n.State).Products.push(n.Product);
}
return p;
}, []);
console.log(grouped);

Sort nested json complex array

I search how to sort by place.city this kind of object who have id's for keys. The need is to keep id's for first keys…
{
"123": {
"place": {
"city": "New York",
"country": "USA"
},
"person": {
"name": "Bob",
"age": 45
}
},
"456": {
"place": {
"city": "Chicago",
"country": "USA"
},
"person": {
"name": "Louis",
"age": 34
}
},
"789": {
"place": {
"city": "Dallas",
"country": "USA"
},
"person": {
"name": "Kevin",
"age": 27
}
}
}
I try some kind of function like this and the expected result is not here.
let result = _(myObject).map(function (value, key) {
return _.defaults({ name: key }, value)
}).sortBy('city').value()
You can't sort an object.. You can, however, convert your object to an array and sort that.
var data ={
"123" : {
"place": {
"city": "New York",
"country": "USA"
},
"person": {
"name": "Bob",
"age": 45
}
},
"456" : {
"place": {
"city": "Chicago",
"country": "USA"
},
"person": {
"name": "Louis",
"age": 34
}
},
"789" : {
"place": {
"city": "Dallas",
"country": "USA"
},
"person": {
"name": "Kevin",
"age": 27
}
}
};
var sortedByPlace = _.sortBy(Object.keys(data).map(k => ({id:k, ...data[k]})), (d)=> d.place.city)
console.log(sortedByPlace);
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.5/lodash.min.js"></script>
It is not possible to sort an object, you need to make it a list.
import { map, flow } from 'lodash'
import { sortBy } from 'lodash/fp'
cities => flow(
map(places, (place, id) => { id, ...place }),
sortBy('city'),
)()
Your second question begs the question (mh...) if you want local sort. That would be
import { mapValues } from 'lodash'
import { sortBy } from 'lodash/fp'
data => mapValues(data, sortBy('place.city'))
You cant sort an object. However you could create a sorted array that contains references to the objects:
const sorted = Object.values(myObject).sort((a, b) => a.place.city.localeCompare(b.place.city));
If you look at this answer, the following should work
var sort = function (prop, arr) {
prop = prop.split('.');
var len = prop.length;
arr.sort(function (a, b) {
var i = 0;
while( i < len ) { a = a[prop[i]]; b = b[prop[i]]; i++; }
if (a < b) {
return -1;
} else if (a > b) {
return 1;
} else {
return 0;
}
});
return arr;
};
sort("place.city", myObject);
I realize the object I have to treat (get from an obscur API) is a little bit more complicated than my first example :/
So the responses you nicely give are not working anymore…
Do you see the subtlety on this new object ?
The point stay to sort by place.city
{
"123": {
0: {
"place": {
"city": "New York",
"country": "USA"
},
"person": {
"name": "Bob",
"age": 45
}
},
1: {
"place": {
"city": "New York",
"country": "USA"
},
"person": {
"name": "James",
"age": 32
}
}
},
"456": {
0: {
"place": {
"city": "Chicago",
"country": "USA"
},
"person": {
"name": "Louis",
"age": 34
}
},
1: {
"place": {
"city": "Chicago",
"country": "USA"
},
"person": {
"name": "Christine",
"age": 65
}
}
},
"789": {
0: {
"place": {
"city": "Dallas",
"country": "USA"
},
"person": {
"name": "Kevin",
"age": 27
}
},
1: {
"place": {
"city": "Dallas",
"country": "USA"
},
"person": {
"name": "Robert",
"age": 55
}
},
2: {
"place": {
"city": "Dallas",
"country": "USA"
},
"person": {
"name": "Danny",
"age": 62
}
}
}
}

parse json based on dynamic value

I have a json that I'd like to parse based on the 'zone' field in order to get a list of region etc through a select input.
var data = {
"zone01": [{
"region": "North",
"state": "New York",
"county": "Albany",
"code": "01"
}, {
"region": "North",
"state": "New York",
"county": "Albany",
"code": "05"
}, {
"region": "North",
"state": "Maine",
"county": "Auburn",
"code": "07"
}],
"zone02": [{
"region": "South",
"state": "Florida",
"county": "Gainseville",
"code": "82"
}, {
"region": "South",
"state": "Florida",
"county": "Macclenny",
"code": "73"
}]
};
I can parse it by using:
function setValues(range, zone, region, state, country){
if(range === 'zone'){
var getRegions = _.groupBy(data.zone02, 'region');
$.each(getRegions, function(k, v){
var region = JSON.stringify(k);
region = region.replace(/"/g,'');
$('select#region').append('<option value="'+region+'">'+region+'</option>');
});
}
}
But what I really need is _.groupBy(data.zone02, 'region') with data.zone02 being data + the function's variable zone
UPDATE
Here's my finished product, sans readability and re-usability: jsFiddle
Use bracket notation to reference a property using a variable.
data[zone]

Categories

Resources