Transforming JSON data from API into useful format? - javascript

I have an API that returns indicator data in this format:
[
{
"concept": "population",
"year": 2012,
"value": 9.5,
"country-name": "Sweden",
},
{
"concept": "education",
"year": 2012,
"value": 12,
"country-name": "Sweden",
},
{
"concept": "population",
"year": 2012,
"value": 5,
"country-name": "Norway",
},
{
"concept": "eduction",
"year": 2012,
"value": 12,
"country-name": "Norway",
}
]
Based on this, I typically need to group this by country and year to get something like:
var data = [
{id : Sweden, population : 9.5, education: 12},
{id : Norway, population : 5, education: 12},
]
Or just by country and year-values as an array:
var data = [
{id : Sweden, values : [ { year : 2012, population : 9.5, education: 12 }, {...} ]},
{id : Norway, values : [ { year : 2012, population : 5, education: 12 }, {...} ]},
]
What JS libraries can help manage these common data transformations?
Can you provide an example of doing the above (using a library or vanilla js)?

You can do it yourself, just iterate and create whatever you need, something like
var data = [];
json.forEach(function(item) {
var country = item['country-name'],
match = false,
obj = {}
data.forEach(function(dataItem, i) {
if (dataItem.id === country) {
match = true;
obj = data[i];
}
});
obj.id = country;
obj[item.concept] = item.value;
if (!match) data.push(obj)
});
FIDDLE

You might find an object easier to work with than an array:
var data = {};
indicatordata.forEach(function(item){
var country = item['country-name'];
var year = item.year;
data[country] = data[country] || {id: item['country-name'], values: {}};
var values = data[country].values;
values[year] = values[year] || {};
values[year][item.concept] = item.value;
});

You can do it with D3. d3.nest and Array.prototype.reduce specifically. The code is declarative and easy to maintain.
var rawData = [
{
"concept": "population",
"year": 2012,
"value": 9.5,
"country-name": "Sweden",
},
{
"concept": "education",
"year": 2012,
"value": 12,
"country-name": "Sweden",
},
{
"concept": "population",
"year": 2012,
"value": 5,
"country-name": "Norway",
},
{
"concept": "eduction",
"year": 2012,
"value": 12,
"country-name": "Norway",
}
];
var data = d3.nest()
.key(function(d)
{
return d['year'];
})
.key(function(d)
{
return d['country-name'];
})
.rollup(function(group)
{
return group.reduce(function(result, d)
{
result[d['concept']] = d['value'];
return result;
}, {});
})
.entries(rawData);
console.log(data);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

Related

What is the most performant way to convert an Array of Object to an Object with unique keys

I am trying to figure out the most performant Javascript way to convert an array of objects, into an object with unique keys and an array full of objects as the value.
For Example:
const array = [
{ "name": "greg", "year": "2000" },
{ "name": "john", "year": "2002" },
{ "name": "bob", "year": "2005" },
{ "name": "ned", "year": "2000" },
{ "name": "pam", "year": "2000" },
];
I would like this converted to:
{
"2000": [
{ "name": "greg", "year": "2000" },
{ "name": "ned", "year": "2000" },
{ "name": "pam", "year": "2000" }
],
"2002": [ { "name": "john", "year": "2002" } ],
"2005": [ { "name": "bob", "year": "2005" } ],
}
As of now, this is what I've done so far:
let yearsObj = {};
for (let i=0; i<array.length; i++) {
if (!yearsObj[array[i].year]) {
yearsObj[array[i].year] = [];
}
yearsObj[array[i].year].push(array[i]);
}
you can use a more elegant way to do it by using array's reduce function
// # impl
const group = key => array =>
array.reduce(
(objectsByKeyValue, obj) => ({
...objectsByKeyValue,
[obj[key]]: (objectsByKeyValue[obj[key]] || []).concat(obj)
}),
{}
);
// # usage
console.log(
JSON.stringify({
byYear: group(array),
}, null, 1)
);
// output
VM278:1 {
"carsByBrand": {
"2000": [
{
"name": "greg",
"year": "2000"
},
{
"name": "ned",
"year": "2000"
},
{
"name": "pam",
"year": "2000"
}
],
"2002": [
{
"name": "john",
"year": "2002"
}
],
"2005": [
{
"name": "bob",
"year": "2005"
}
]
}
}
It could be as simple as that Object.fromEntries(array.map(obj => [obj.year,obj])) even it is not exactly what you need, but talking about performance it is way slower than all proposed, so i'm giving it as an bad example of showing how the short statement is not always the fastest.
Your way seems to be the fastest taking about performance.
Run the snippet below to see the actual timing.
// common
let array = [
{ "name": "greg", "year": "2000" },
{ "name": "john", "year": "2002" },
{ "name": "bob", "year": "2005" },
{ "name": "ned", "year": "2000" },
{ "name": "pam", "year": "2000" },
];
// simple as a statement way
console.time();
console.log(Object.fromEntries(array.map(obj => [obj.year,obj])));
console.timeEnd();
// using .reduce way
console.time();
const result = array.reduce((prev, curr) => {
const { year } = curr;
if (prev[year]) {
prev[year].push(curr);
} else {
prev[year] = [curr];
}
return prev;
}, {});
console.log(result);
console.timeEnd();
// your way
console.time();
let yearsObj = {};
for (let i=0; i<array.length; i++) {
if (!yearsObj[array[i].year]) {
yearsObj[array[i].year] = [];
}
yearsObj[array[i].year].push(array[i]);
}
console.log(yearsObj);
console.timeEnd();
A for loop (imperative style) like you have is likely to be the fastest in most situations. However, in this case you are not likely to see much of a difference. One thing you could do to improve the code in your example is to get the array length before the for loop and assign it to the variable, so that it's not calculated every iteration of the loop.
const yearsObj = {};
const arrayLength = array.length; // Only calculate array length once
for (let i=0; i<arrayLength; i++) {
if (!yearsObj[array[i].year]) {
yearsObj[array[i].year] = [];
}
yearsObj[array[i].year].push(array[i]);
}
In this situation, my preference would be to use Array.reduce(). It is more readable and the performance difference will be negligible.
const arr = [
{ name: 'greg', year: '2000' },
{ name: 'john', year: '2002' },
{ name: 'bob', year: '2005' },
{ name: 'ned', year: '2000' },
{ name: 'pam', year: '2000' },
];
const result = arr.reduce((prev, curr) => {
const { year } = curr;
if (prev[year]) {
prev[year].push(curr);
} else {
prev[year] = [curr];
}
return prev;
}, {});
/* Result:
{ '2000':
[ { name: 'greg', year: '2000' },
{ name: 'ned', year: '2000' },
{ name: 'pam', year: '2000' } ],
'2002': [ { name: 'john', year: '2002' } ],
'2005': [ { name: 'bob', year: '2005' } ] }
*/

Filter JSON Obj

I'm getting a JSON object from a XMLHttpRequest and am looking for a solution to filter bt genre. I also need a way to return all results (unfiltered) if no filterable parameter is passed.
app.js (pseudo function)
function filterObject(json, filterBy) {
// filter through json
// return item that matches filterBy
}
data.json
[{
"date": {
"dayOfWeek": "Thursday",
"month": "Oct"
},
"location": "Bristol",
"genre": "rock"
}, {
"date": {
"dayOfWeek": "Cardiff",
"dayOfMonth": 13,
"month": "Oct"
},
"location": "Manchester",
"genre": "jazz"
}]
You can use array#filter to filter your json on genre value.
var json = [{"date": {"dayOfWeek": "Thursday","month": "Oct"},"location": "Bristol","genre": "rock"}, {"date": {"dayOfWeek": "Cardiff","dayOfMonth": 13,"month": "Oct"},"location": "Manchester", "genre": "jazz"}];
function filterObject(json, filterBy) {
return filterBy ? json.filter(o => o.genre === filterBy) : json;
}
console.log(filterObject(json,'jazz'));
console.log(filterObject(json));
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can use Array#filter
Something like this:
(function() {
var json = [{
"date": {
"dayOfWeek": "Thursday",
"month": "Oct"
},
"location": "Bristol",
"genre": "rock"
}, {
"date": {
"dayOfWeek": "Cardiff",
"dayOfMonth": 13,
"month": "Oct"
},
"location": "Manchester",
"genre": "jazz"
}];
function filterObject(json, filterBy) {
if (filterBy !== undefined) {
return json.filter(function(x) {
return x.genre === filterBy;
});
} else {
return json;
}
}
var resultWithParameter = filterObject(json, "jazz");
console.log(resultWithParameter);
var resultWithoutParameter = filterObject(json);
console.log(resultWithoutParameter);
})();
You can use filterBy as object with key and value properties to find any property you like (not only genre key).
function filterObject(data, filterBy) {
if(typeof filterBy === 'undefined') return data;
return data.filter(function(item) {
return item[filterBy.key] === filterBy.value;
})
}
Test:
filterObject(arr, {key: 'genre', value: 'rock'}); // return rock band
filterObject(arr, {key: 'genre', value: 'jazz'}); // jazz band
filterObject(arr, {key: 'location', value: 'Bristol'}); // return band with location key
filterObject(arr); // return initial array

Grouping array of objects by multiple keys

I feel embarrassed for asking this question as I should know how to figure it out, but I'm spinning my wheels on grouping an array of objects by multiple keys.
Here's the data:
[
{
"car": "audi",
"type": "A6",
"style": "Avant",
"year": "1996"
},
{
"car": "audi",
"type": "A4",
"style": "2",
"year": "2006"
},
{
"car": "audi",
"type": "A4",
"style": "L W12",
"year": "2006"
},
{
"car": "audi",
"type": "80",
"style": "GLE",
"year": "1975"
},
{
"car": "audi",
"type": "A6",
"style": "Avant L",
"year": "1996"
},
{
"car": "audi",
"type": "A6",
"style": "3.2 Multitronic",
"year": "2006"
},
]
What I've been trying to generate with little success is the following:
[{
"audi": [{
"1996": {
"A6": ["Avant, Avant L"]
}
}, {
"2006": }
"A6": ["3.2 Multitronic"],
"A4": ["L W12", "2"]
}
}
....
}]
The schema is:
{
"car1": [{
"year1": {
"style1": ["trim1", "trim2"],
"style2": ["trim1", "trim2"]
},
"year1": {
"style1": ["trim1", "trim2"],
"style2": ["trim1", "trim2"]
}
}],
"car2": [{
"year1": {
"style1": ["trim1", "trim2"],
"style2": ["trim1", "trim2"]
},
"year2": {
"style1": ["trim1", "trim2"],
"style2": ["trim1", "trim2"]
}
}]
}
I've tried the following with lodash
let result = _.chain(carData)
.groupBy('car')
.toPairs()
.map(function(curr) {
return _.zipObject(['car', 'year'], curr);
})
.value();
This gets me part of the way, but I end up with incomplete data when it comes to the styles and types for each year of the car.
You could use a hash object and a nested approach for the given properties.
var data = [{ car: "audi", type: "A6", style: "Avant", year: 1996 }, { car: "audi", type: "A4", style: 2, year: 2006 }, { car: "audi", type: "A4", style: "L W12", year: 2006 }, { car: "audi", type: 80, style: "GLE", year: 1975 }, { car: "audi", type: "A6", style: "Avant L", year: 1996 }, { car: "audi", type: "A6", style: "3.2 Multitronic", year: 2006 }],
keys = ['car', 'year', 'type'],
result = [];
data.forEach(function (a) {
keys.reduce(function (r, k) {
var o = {};
if (!r[a[k]]) {
r[a[k]] = { _: [] };
o[a[k]] = r[a[k]]._;
r._.push(o);
}
return r[a[k]];
}, this)._.push(a.style);
}, { _: result });
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Here's a (slightly verbose) solution that generates exactly the JSON object shape you wanted and groups by unlimited keys:
var cars = [{
"car": "audi",
"type": "A6",
"style": "Avant",
"year": "1996"
}, {
"car": "audi",
"type": "A4",
"style": "2",
"year": "2006"
}, {
"car": "audi",
"type": "A4",
"style": "L W12",
"year": "2006"
}, {
"car": "audi",
"type": "80",
"style": "GLE",
"year": "1975"
}, {
"car": "audi",
"type": "A6",
"style": "Avant L",
"year": "1996"
}, {
"car": "audi",
"type": "A6",
"style": "3.2 Multitronic",
"year": "2006"
}, ];
function groupBy(list, prop) {
return list.reduce((groupped, item) => {
var key = item[prop];
delete item[prop];
if (groupped.hasOwnProperty(key)) {
groupped[key].push(item);
} else {
groupped[key] = [item];
}
return groupped
}, {});
}
function groupSubKeys(obj, properties, propIndex) {
var grouppedObj = groupBy(obj, properties[propIndex]);
Object.keys(grouppedObj).forEach((key) => {
if (propIndex < properties.length - 2) {
grouppedObj[key] = groupSubKeys(grouppedObj[key], properties, propIndex + 1);
} else {
grouppedObj[key] = grouppedObj[key].map(item => item[properties[propIndex + 1]])
}
});
return grouppedObj;
}
function groupByProperties(list, properties) {
return groupSubKeys(list, properties, 0);
}
console.log(groupByProperties(cars, ['car', 'year', 'type', 'style']));
Here's a running example:
http://codepen.io/rarmatei/pen/evmBOo
const groupBy = function groupBy(list, properties, propertyIndex) {
// current property index
let i = propertyIndex === undefined ? 0 : propertyIndex;
// group by
let grouppedObj = list.reduce((acc, obj) => {
let groupedValue = obj[properties[i]];
if (!groupedValue) {
return acc;
}
if (!acc[groupedValue]) {
acc[groupedValue] = [];
}
acc[groupedValue].push({ ...obj, groupBy: properties.join(",") });
return acc;
}, {});
// group by nested
const keys = Object.keys(grouppedObj);
if (i === properties.length - 1) {
return grouppedObj;
}
keys.forEach((key) => {
grouppedObj[key] = groupBy(grouppedObj[key], properties, i + 1);
});
return grouppedObj;
};
const data =[
{
"year": "2021",
"cabin": "1",
"months": ["1", "2"]
},
{
"year": "2021",
"cabin": "1",
"months": ["4"]
},
{
"year": "2021",
"cabin": "2",
"months": ["1", "2"]
},
{
"year": "2022",
"cabin": "1",
"months": ["1", "2"]
},
{
"year": "2022",
"cabin": "1",
"months": ["4"]
},
{
"year": "2022",
"cabin": "2",
"months": ["1", "2"]
}
];
const results=groupBy(data, ["year", "cabin"]);
console.log(results);

Plot REST API response using Plotly and AngularJS

How to assign a and b values from the json {month,consumption}?
REST API response:
data= {
"_id": {
"$oid": "580b8f80c2ef1645d285c884"
},
"year": "2012",
"state": "Indiana",
"consumption": 3275,
"month": "january"
},
{
"_id": {
"$oid": "580b8f81bd966f4110af0111"
},
"year": "2012",
"state": "Indiana",
"consumption": 6348,
"month": "february"
}
app.js:
var graphPlotterDemoApp = angular.module('graphPlotterDemoApp', ['graphPlotter']);
graphPlotterDemoApp.controller('graphPlotterDemoCtrl', function($scope, $http) {
var a = ["jan", "feb", "mar", "apr"];
var b = [10, 15, 13, 17];
$scope.data={};
$scope.refresh = function(){
$http.get("https://api.mlab.com/api/1/databases/energyx/collections/energydata_2012?&apiKey=6JzRAzvdeqyLKAfQAqMOsLYLvvIdJG6A")
.success(function(response) {
$scope.data = response.data;
});
};
var trace1 = {
x: a,
y: b,
type: 'scatter'
};
$scope.refresh();
$scope.graphPlots = [trace1];
});
Use like to get data from json
var a=$scope.data.map(function(a) {return a.month;})
var b=$scope.data.map(function(a) {return a.consumption;})

How count objects with element value?

I need your help.
I have for example such array:
var array = [{
"name": "Tony",
"year": "2010"
}, {
"name": "Helen",
"year": "2010"
}, {
"name": "Jack",
"year": "2005"
}, {
"name": "Tony",
"year": "2008"
}, {
"name": "Max",
"year": "2005"
}];
How i can count them by year and get something like this:
2010 = 2 times;
2005 = 2 times;
2008 = 1 time;
Thank you
check this fiddle
var countObj = {};
for( var counter = 0; counter < array.length; counter++ )
{
var yearValue = array [ counter ].year;
if ( !countObj[ yearValue ] )
{
countObj[ yearValue ] = 0;
}
countObj[ yearValue ] ++;
}
console.log( countObj );
Try this:
var array = [{
"name": "Tony",
"year": "2010"
}, {
"name": "Helen",
"year": "2010"
}, {
"name": "Jack",
"year": "2005"
}, {
"name": "Tony",
"year": "2008"
}, {
"name": "Max",
"year": "2005"
}];
var map={}
for (var i = 0; i<array.length;i++){
if ( !map[ array [ i ].year ] ){
map[ array [ i ].year ] = 0;
}
map[ array [ i ].year] ++;
}
console.log( map );
Fiddle
Here arrange uses reduce to build an object using the years as keys. It accepts a prop argument so that you can build the object as you see fit.
function arrange(arr, prop) {
return arr.reduce(function(p, c) {
var key = c[prop];
p[key] = p[key] || 0;
p[key]++;
return p;
}, {});
}
You can then iterate over the key/values of that object and print out the results for year:
var obj = arrange(array, 'year');
for (var p in obj) {
console.log(p + ' = ' + obj[p] + ' times');
}
Or even by name:
var obj = arrange(array, 'name');
DEMO
You are trying to re-invent the wheel.
This and many more methods are exposed by a third-party JS library, called "lodash"; old name was "underscore". All its methods or most of them are exposed like
_.methodName(listName, iteratorFunction)
You can download it at: https://lodash.com/
Once you download and include lodash your script in your html, go to your function and do:
_.groupBy(yourArrayVariable, function(item){
return item.year;
});
WARNING
This method will not return an array. It will return a JSON, in which the keys are represented by the "item.year" through the whole original array, and the values will be an array for each year listed. Every such array is a list of objects having the same "year" value:
{
"2010" : [
{ "name" : "Tony", "year" : "2010" },
{ "name" : "Helen", "year" : "2010" }
],
"2011" : [ ..... ]
}
Lodash has lots of useful methods anyway.
var array = [{
"name": "Tony",
"year": "2010"
}, {
"name": "Helen",
"year": "2010"
}, {
"name": "Jack",
"year": "2005"
}, {
"name": "Tony",
"year": "2008"
}, {
"name": "Max",
"year": "2005"
}];
var result = {};
array.forEach(function(data) {
console.log(data.year);
result[data.year] = result[data.year] ? ++result[data.year] : 1;
});
document.write(JSON.stringify(result));

Categories