Problem
I’m building a web interface for a home monitoring sensor array, and fetching JSON from the backend framework. I want to start putting statistics together for monitoring different areas of the home, inside and outside.
Each thermal sensor provide the reading, in regular intervals, of temperature and time of day, so I end up with a JSON object looking like this:
{
"sensor_1" : [
{
"location" : "garage_east",
"temp_f" : [{
"value" : "71.2",
"timestamp" : "2017-05-01T08:15:00"
},
{
"value" : "70.8",
"timestamp" : "2017-05-01T08:30:00"
},
{
"value" : "72.6",
"timestamp" : "2017-05-01T08:45:00"
}
]}],
"sensor_2" : [{
"location" : "kitchen",
"temp_f" : [{
"value" : "78.2",
"timestamp" : "2017-05-01T08:15:00"
},
{
"value" : "78.8",
"timestamp" : "2017-05-01T08:30:00"
},
{
"value" : "78.6",
"timestamp" : "2017-05-01T08:45:00"
}]
}]}
and so on, with multiple sensors each displaying pretty large JSON objects for the reporting periods.
I want to derive the highest and lowest temperatures around the house, and at what time they occurred, using JavaScript (with AngularJS). Currently, I’m looking to optimize this (which does work):
/* The entire array of data, within time frame set by the user
* and for the sensors defined by the user
* from the RESTful API (params)
*/
api.sensor_data.get(params, function(data){
$scope.sensorStats = {
"kitchen" : {},
"garage" : {}
}
/* find the highest temperature for a single sensor */
let hightemp_kitchen = Math.max.apply(Math, data.sensor_1.temp_f.map(function(t){
return t.value;
}));
/* grab the associated timestamp and put them both into an object */
$scope.sensorStats.kitchen.high = data.sensor_1.temp_f.find(function(obj){
return obj.value == hightemp_kitchen;
});
/* Repeat for each sensor of concern (user defined in params) */
etc...
});
}, ...
and then I use the in the DOM the expression {{ sensorStats.kitchen.high }} to display the high temp and date/time to the user for the kitchen.
Like I say, this works, but I’m doing this for each of the 15+ sensors in the array around the property. This method not the easiest to read and maintain – I think it can be faster and more efficient to maintain, but I don’t have the lexicon to know what to research.
Is this efficient? Is there a “better” or more efficient way to filter out the data and return the temp and time? I’m reading up on ES6, and discovered the filter() method, but it seems to be useful only on arrays and not objects.
Solution
It seems like you should not be hard-coding concepts such as “kitchen”, “garage”, etc. into your code at all. You need to design that out.
Also I question the JSON structure. sensor_*
are meaningless property names, giving you an unnecessary level of nesting, so if you have control over JSON generation, you might consider an array of object like:
[
{
"location": ...,
"temp_f": [
...
]
},
{
...
}
]
But let’s assume you can’t do that, and think of ways we can get you the information make this whole thing more dynamic. I am not sure what params
is even used for, so I am discarding this.
api.sensor_data.get(function(data){
let sensorData = {};
// loop through each sensoer object, ignore top level key value
Object.values(data).forEach( (sensor) => {
// reduce f_temp array to single entry with highest temp
// and set to sensorData result object with location as key
sensorData[sensor.location] = sensor.f_temp.reduce( (acc, metric) => {
if(parseFloat(metric.value) > parseFloat(acc.value)) acc = metric;
return acc;
}, sensor.f_temp[0]);
});
$scope.sensorStats = sensorData;
}
Based on your comment regarding params
, you might have something more like this:
let sensorData = {};
let sensors = Object.values(data);
// filter by location
if (location in params) {
sensors = sensors.filter( (sensor) => sensor.location === params.location );
}
// loop through each sensor objects
sensors.forEach( (sensor) => {
let result = null;
let metrics = sensor.f_temp;
if(minTimestamp in params) {
metrics = metrics.filter( metric => {
return (Date.parse(metric.timestamp) >= params.minTimeStamp );
});
}
if(maxTimestamp in params) {
metrics = metrics.filter( metric => {
return (Date.parse(metric.timestamp) =< params.maxTimeStamp );
});
}
if(metrics.length > 0) {
result = metrics.reduce( (acc, metric) => {
if(parseFloat(metric.value) > parseFloat(acc.value)) acc = metric;
return acc;
}, metrics[0]);
sensorData[sensor.location] = result;
});
$scope.sensorStats = sensorData;
}
The solution depends on the desired output format. My example:
const a = {
"sensor_1" : [
{
"location" : "garage_east",
"temp_f" : [{
"value" : "71.2",
"timestamp" : "2017-05-01T08:15:00"
},
{
"value" : "70.8",
"timestamp" : "2017-05-01T08:30:00"
},
{
"value" : "72.6",
"timestamp" : "2017-05-01T08:45:00"
}
]}],
"sensor_2" : [{
"location" : "kitchen",
"temp_f" : [{
"value" : "78.2",
"timestamp" : "2017-05-01T08:15:00"
},
{
"value" : "78.8",
"timestamp" : "2017-05-01T08:30:00"
},
{
"value" : "78.6",
"timestamp" : "2017-05-01T08:45:00"
}]
}]}
const b = Object.keys(a)
.map(k => {
const key = a[k][0].location;
const value = a[k][0].temp_f.sort((a, b) => a.value - b.value).pop();
return { [key]: value }
})
console.log(JSON.stringify(b, null, 2))