Group an array of objects based on multi keys in Javascript? - 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);

Related

Hierarchy grouping on JSON object using reduce

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)
)

How to fomat nested Json using Vanilla Js

I want to transform the Input JSON to Output Array of Objects, Use Input JSON array and use only vanilla Javascript, loop through JSON object. I tried foreach function but faced some issues. print output like given below
let Input ={
details:[
{
"id":"Country_name",
"values":[
"India",
"England",
"Germany"
]
},
{
"id":"Country_capital",
"values":[
"Delhi",
"London",
"Berlin"
]
}
],
metadata:[
{
"id":"Country_name",
"label":"Country"
},
{
"id":"Country_capital",
"label":"Capital"
}
]
}
let Output =[
{
"Country":"India",
"Capital":"Delhi"
},
{
"Country":"England",
"Capital":"London"
},
{
"Country":"Germany",
"Capital":"Berlin"
}
]
Object.keys(input).forEach(function(value, key) {
input[value].forEach(function(v, k) {
console.log(v.id)
})
})
You can try something like this
const input = {
details: [{
"id": "Country_name",
"values": [
"India",
"England",
"Germany"
]
},
{
"id": "Country_capital",
"values": [
"Delhi",
"London",
"Berlin"
]
}
],
metadata: [{
"id": "Country_name",
"label": "Country"
},
{
"id": "Country_capital",
"label": "Capital"
}
]
};
function transform(input) {
const ids = {};
for (const detail of input.details) {
ids[detail.id] = detail.values;
}
const meta = {};
for (const m of input.metadata) {
meta[m.id] = m.label;
}
const idsKeys = Object.keys(ids);
const out = [];
for (let i = 0; i < ids[idsKeys[0]].length; i++) {
const obj = {};
for (const key of idsKeys) {
obj[meta[key]] = ids[key][i];
}
out.push(obj);
}
return out;
}
console.log(transform(input));
I changed the input and the variable in the top
This may work for you or not
let input ={
details:[
{
"id":"Country_name",
"values":[
"India",
"England",
"Germany"
]
},
{
"id":"Country_capital",
"values":[
"Delhi",
"London",
"Berlin"
]
}
],
metadata:[
{
"id":"Country_name",
"label":"Country"
},
{
"id":"Country_capital",
"label":"Capital"
}
]
}
let Output =[
{
"Country":"India",
"Capital":"Delhi"
},
{
"Country":"England",
"Capital":"London"
},
{
"Country":"Germany",
"Capital":"Berlin"
}
]
Object.keys(input).forEach(function(value, key) {
input[value].forEach(function(v, k) {
console.log(v.id)
})
})
If you have anymore questions or problems I would be happy to help
Countries are in Input.details[0].values
Capitals are in Input.details[1].values
Just make an object containing country on one go through countries, then add capital to each one on second pass through capitals
let Input = {
details: [{
"id": "Country_name",
"values": [
"India",
"England",
"Germany"
]
},
{
"id": "Country_capital",
"values": [
"Delhi",
"London",
"Berlin"
]
}
],
metadata: [{
"id": "Country_name",
"label": "Country"
},
{
"id": "Country_capital",
"label": "Capital"
}
]
};
let Output = [{
"Country": "India",
"Capital": "Delhi"
},
{
"Country": "England",
"Capital": "London"
},
{
"Country": "Germany",
"Capital": "Berlin"
}
];
const newOutput = [];
const countries = Input.details[0].values;
countries.forEach(country => {
console.log(country);
newOutput.push({"Country": country});
});
const capitals = Input.details[1].values;
capitals.forEach((capital, i) => {
console.log(capital);
newOutput[i]["Capital"] = capital;
});
console.log(newOutput);
a shorthand method to do this is mapping the data arrays of input :
let input ={
details:[
{
"id":"Country_name",
"values":[
"India",
"England",
"Germany"
]
},
{
"id":"Country_capital",
"values":[
"Delhi",
"London",
"Berlin"
]
}
],
metadata:[
{
"id":"Country_name",
"label":"Country"
},
{
"id":"Country_capital",
"label":"Capital"
}
]
}
let Output =[
{
"Country":"India",
"Capital":"Delhi"
},
{
"Country":"England",
"Capital":"London"
},
{
"Country":"Germany",
"Capital":"Berlin"
}
]
let data = input.details
let countries = data[0].values
let capitals = data[1].values
const output = countries.map((el, index) => ({"country": el, "capital": capitals[index]}))
console.log(output)
However, in your code, you are referring to Input object as input , which are two different variables

Trying to iterate between 2 var obj and merge them

I have:
const dictionary = [
{
"state": "AK",
"lat": "9875.33.00",
"long": "-8371.42.00",
"name": "Alaska"
},
{
"state": "AL",
"lat": "5335.51.00",
"long": "-15124.18.00",
"name": "Alabama"
}
];
const data = [
{
"date": 20200421,
"state": "AK",
},
{
"date": 20200421,
"state": "AL",
}
];
const result = data.map(item => ({...item, lat: dictionary[item.state].lat, long: dictionary[item.state].long }))
console.log(result);
Basically trying to add dictionary as objs per each data where the state matches but I'm having:
Cannot read property 'lat' of undefined
Expecting:
const result = [
{
"date": 20200421,
"state": "AK",
"lat": "9875.33.00",
"long": "-8371.42.00",
},
{
"date": 20200421,
"state": "AL",
"lat": "5335.51.00",
"long": "-15124.18.00",
}
];
I'm trying on fiddle
You cannot access the array items as one whole array. You have to isolate you desired obj. Use this.
const result = data.map(function(item) {
const ditem = dictionary.find(d => d.state == item.state);
if(ditem) {
return {
...item,
"lat": ditem.lat ,
"long": ditem.long
}
}
return item;
});
console.log(result);
You could take an object with state as key and merge new objects by taking name out of the object.
const
dictionary = [{ state: "AK", lat: "9875.33.00", long: "-8371.42.00", name: "Alaska" }, { state: "AL", lat: "5335.51.00", long: "-15124.18.00", name: "Alabama" }],
data = [{ date: 20200421, state: "AK" }, { date: 20200421, state: "AL" }],
states = data.reduce((r, o) => (r[o.state] = o, r), {}),
merged = dictionary.map(({ name, ...o }) => ({ ...(states[o.state] || {}), ...o }));
console.log(merged);
.as-console-wrapper { max-height: 100% !important; top: 0; }
const dictionary = [
{
"state": "AK",
"lat": "9875.33.00",
"long": "-8371.42.00",
"name": "Alaska"
},
{
"state": "AL",
"lat": "5335.51.00",
"long": "-15124.18.00",
"name": "Alabama"
}
];
const data = [
{
"date": 20200421,
"state": "AK",
},
{
"date": 20200421,
"state": "AL",
}
];
let result = dictionary.filter(element =>{
for(let i = 0; i < data.length; i++){
if(data[i].state == element.state) return element;
}
})
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);

Categories

Resources