I'd like to transform a nested JSON object for a time series D3 multiline chart. I'd like to transform this:
[
{
"utc_date": "2012-12-13T00:00:00.000Z",
"data": {
"view": {
"count": 9061
},
"purchase": {
"count": 254
}
}
},
{
"utc_date": "2012-12-14T00:00:00.000Z",
"data": {
"view": {
"count": 17232
},
"purchase": {
"count": 539
}
}
},
{
"utc_date": "2012-12-15T00:00:00.000Z",
"data": {
"view": {
"count": 28783
},
"purchase": {
"count": 936
}
}
}]
to something like this (or better?)
[
{
"key": "view",
"data": [
{
"date": "2012-12-13T00:00:00.000Z",
"count": 9061
},
{
"date": "2012-12-14T00:00:00.000Z",
"count": 17232
},
{
"date": "2012-12-15T00:00:00.000Z",
"count": 28783
}
]
},
{
"key": "purchase",
"data": [
{
"date": "2012-12-13T00:00:00.000Z",
"count": 254
},
{
"date": "2012-12-14T00:00:00.000Z",
"count": 539
},
{
"date": "2012-12-15T00:00:00.000Z",
"count": 936
}
]
}
]
To be clear, "view" and "purchase" will not be known and are dynamic. So they shouldn't be hardcoded into the example.
But still wondering if there is any D3.js method or chain of methods to transform this data?
Assuming your original array is data, you could do something like this:
V=data.map(function(d){
w=d.data;
M=Array();
for (e in w) { // to loop for all parameters
M.push({"date":d.utc_date, "param":e,"count":w.view.count})
}
return M;
})
V=d3.merge(V)
V=d3.nest().key(function(d){return d.param}).entries(V);
V will hold your new data structure.
I am sure that this can be further improved.
EDIT
Perhaps you don't even need the d3.merge if you only append to another Array to begin with.
M=Array();
V=data.map(function(d){
w=d.data;
for (e in w) {
M.push({"date":d.utc_date, "param":e,"count":w.view.count})
}
})
V=d3.nest().key(function(d){return d.param}).entries(M);
I will let you experiement and see the difference in the performance and code structure.
Hope it helps
Just improving #Nikos answer below to cater for dynamic keys and, although it doesn't use a D3 method chain, this worked:
var result = [];
data.forEach(function(d) {
w = d.data;
for (var k in w) {
if (w.hasOwnProperty(k)) {
result.push({
"date":d.utc_date,
"type":k,
"count":w[k].count
});
}
}
});
JSFiddle here: http://jsfiddle.net/3umm391n/8/
Related
I want to write a recursive function to iterate through a json object using javascript.
I have a sample json file:
{
"week": [
[
{
"id": "121",
"amount": 50,
"numberOfDays": 7,
"data": {
"supply": "xyz",
"price": 50,
}
}
],
[
{
"id": "122",
"amount": 30,
"numberOfDays": 6,
"data": {
"supply": "xyz",
"price": 30,
}
}
],
]
}
I want to take each element of the json object array and pass it to a function.
To extract the array elements I am using this code:
for(var i=0;i<array[plan].length; i++){
var confPlan = array[plan][i];
console.log(plan);
}
var Bill = function (plan) {
return func(plan)
.then((status) => {
if(status == '1') {
// do something
} else if(status == '0') {
Bill(plan) // but with the next element of the array from the json file
}
})
}
please Help!
Thanks in Advance.
It seems your question boils down to wanting to be able to synchronously chain calls to an asynchronous function. I am assuming func is an asynchronous function since your using a .then, so I simulated it with a timeout. Below is one way to achieve the desired behavior recursively:
data = {
"week": [
[{
"id": "121",
"amount": 50,
"numberOfDays": 7,
"data": {
"supply": "xyz",
"price": 50,
}
}],
[{
"id": "122",
"amount": 30,
"numberOfDays": 6,
"data": {
"supply": "xyz",
"price": 30,
}
}],
]
};
function func(data) {
return new Promise((resolve) => {
setTimeout(function() {
console.log(data);
resolve(0);
}, 1000)
})
}
function loop(data, i = 0) {
if (i < data.length) {
func(data[i])
.then((status) => {
if (status) {
// do something
} else {
return loop.call(null, data, i += 1)
}
})
}
}
loop(data['week']);
The ideal solution involves using async/await iteratively in a for loop. Not only will it avoid recursion but the structure of the code is cleaner and more familiar. However, I will leave this to you to research.
Thanks ahead for your help!
I'm trying to reorder some objects so the newest ordered product would be first. So looking at the data below, the new order should be (from newest to oldest) product2, product3, and then product1.
{
"candyResponse": {
"product1": {
"displayName": "Bubble Gum",
"imageURL": "https://dummyimage.com/200x200/000/ffffff.png&text=prod1?$146$",
"orderDate": {
"time": "11/03/2018"
}
},
"product2": {
"displayName": "Chocolate",
"imageURL": "https://dummyimage.com/200x200/000/ffffff.png&text=prod2?$146$",
"orderDate": {
"time": "03/05/2015"
}
},
"product3": {
"displayName": "Mints",
"imageURL": "https://dummyimage.com/200x200/000/ffffff.png&text=prod3?$146$",
"orderDate": {
"time": "09/20/2017"
}
}
}
}
I tweaked the code from Sorting an array of JavaScript objects but I was not successful. Tried three ways...
candyResponse.sort(function(a, b) {
return parseFloat(a.time) - parseFloat(b.time);
});
candyResponse.sort(function(a, b) {
return parseFloat(a.orderDate) - parseFloat(b.orderDate);
});
candyResponse.sort(function(a, b) {
return parseFloat(a.orderDate.time) - parseFloat(b.orderDate.time);
});
Thanks again for your help!
candyResponse is an object, not an array - objects' property names are not reliably ordered, per the spec, so even if you did create a new object with the properties in the desired insertion order, it wouldn't be something to rely on.
Sort an array instead:
const candyResponse = {
"product1": {
"displayName": "Bubble Gum",
"imageURL": "https://dummyimage.com/200x200/000/ffffff.png&text=prod1?$146$",
"orderDate": {
"time": "11/03/2018"
}
},
"product2": {
"displayName": "Chocolate",
"imageURL": "https://dummyimage.com/200x200/000/ffffff.png&text=prod2?$146$",
"orderDate": {
"time": "03/05/2015"
}
},
"product3": {
"displayName": "Mints",
"imageURL": "https://dummyimage.com/200x200/000/ffffff.png&text=prod3?$146$",
"orderDate": {
"time": "09/20/2017"
}
}
};
const getTime = ([_, product]) => {
return new Date(product.orderDate.time);
};
const sortedCandyArr = Object.entries(candyResponse)
.sort((a, b) => getTime(b) - getTime(a));
console.log(sortedCandyArr);
Well, you don't have an array of javascript objects, you just have a javascript object. That being said, you could do:
var data = {
"candyResponse": {
"product1": {
"displayName": "Bubble Gum",
"imageURL": "https://dummyimage.com/200x200/000/ffffff.png&text=prod1?$146$",
"orderDate": {
"time": "11/03/2018"
}
},
"product2": {
"displayName": "Chocolate",
"imageURL": "https://dummyimage.com/200x200/000/ffffff.png&text=prod2?$146$",
"orderDate": {
"time": "03/05/2015"
}
},
"product3": {
"displayName": "Mints",
"imageURL": "https://dummyimage.com/200x200/000/ffffff.png&text=prod3?$146$",
"orderDate": {
"time": "09/20/2017"
}
}
}
}
var sorted = Object.keys(data.candyResponse)
.sort((a, b) =>
new Date(data.candyResponse[a].orderDate.time).getTime() -
new Date(data.candyResponse[b].orderDate.time).getTime());
data.candyResponse = sorted.reduce((carry, current) => { carry[current] = data.candyResponse[current]; return carry; }, {});
console.log(data)
I have an array with objects, like the following.
b = {
"issues": [{
"fields": {
"status": {
"id": "200",
"name": "Backlog"
}
}
}, {
"fields": {
"status": {
"id": "202",
"name": "close"
}
}
}, {
"fields": {
"status": {
"id": "201",
"name": "close"
}
}
}]
};
I want to count how many issues have status close, and how many have backlog. I'd like to save the count in a new array as follows.
a = [
{Name: 'Backlog', count: 1},
{Name: 'close', count: 2}
];
I have tried the following.
b.issues.forEach(function(i) {
var statusName = i.fields.status.name;
if (statusName in a.Name) {
a.count = +1;
} else {
a.push({
Name: statusName,
count: 1
});
}
});
That however doesn't seem to be working. How should I implement this?
This is a perfect opportunity to use Array#reduce. That function will take a function that is applied to all elements of the array in order and can be used to accumulate a value. We can use it to accumulate an object with the various counts in it.
To make things easy, we track the counts in an object as simply {name: count, otherName: otherCount}. For every element, we check if we already have an entry for name. If not, create one with count 0. Otherwise, increment the count. After the reduce, we can map the array of keys, stored as keys of the object, to be in the format described in the question. See below.
var b = {
"issues": [{
"fields": {
"status": {
"id": "200",
"name": "Backlog"
}
}
}, {
"fields": {
"status": {
"id": "202",
"name": "close"
}
}
}, {
"fields": {
"status": {
"id": "201",
"name": "close"
}
}
}]
};
var counts = b.issues.reduce((p, c) => {
var name = c.fields.status.name;
if (!p.hasOwnProperty(name)) {
p[name] = 0;
}
p[name]++;
return p;
}, {});
console.log(counts);
var countsExtended = Object.keys(counts).map(k => {
return {name: k, count: counts[k]}; });
console.log(countsExtended);
.as-console-wrapper {
max-height: 100% !important;
}
Notes.
Array#reduce does not modify the original array.
You can easily modify the function passed to reduce to for example not distinguish between Backlog and backlog by changing
var name = c.fields.status.name;
into
var name = c.fields.status.name.toLowerCase();
for example. More advanced functionality can also easily be implemented.
Using ES6 Arrow functions you can do it with minimum syntax
var b = {
"issues": [{
"fields": {
"status": {
"id": "200",
"name": "Backlog"
}
}
}, {
"fields": {
"status": {
"id": "202",
"name": "close"
}
}
}, {
"fields": {
"status": {
"id": "201",
"name": "close"
}
}
}]
};
var countOfBackLog = b.issues.filter(x => {
return x.fields.status.name === "Backlog"
}).length
var countOfClose = b.issues.filter(x => {
return x.fields.status.name === "close"
}).length
a =[{Name: 'Backlog', count : countOfBackLog}, {Name: 'close', count : countOfClose}]
More about arrow functions here
You can write like this. It is dynamic.
var a = {};
for(var key in b["issues"]){
if(!a.hasOwnProperty(b["issues"][key].fields.status.name)){
a[b["issues"][key].fields.status.name] = 1;
}else{
a[b["issues"][key].fields.status.name] = a[b["issues"][key].fields.status.name]+1;
}
}
var c = [];
for(var key1 in a){
c.push({
name : key1,
count : a[key1]
});
}
Something like this should do the trick. Simply iterate over your data, keep 2 counters with the number of each type of issue, and create the data format you want in the end. Try it live on jsfiddle.
var b = {
"issues": [{
"fields": {
"status": {
"id": "200",
"name": "Backlog"
}
}
}, {
"fields": {
"status": {
"id": "202",
"name": "close"
}
}
}, {
"fields": {
"status": {
"id": "201",
"name": "close"
}
}
}]
};
var data = [];
for(var issue of b.issues){
var entryFound = false;
var tempObj = {
name: issue.fields.status.name,
count: 1
};
for(var item of data){
if(item.name === tempObj.name){
item.count++;
entryFound = true;
break;
}
}
if(!entryFound){
data.push(tempObj);
}
}
console.log(data);
This is my function. now its suck when there is 200 id.i don't want to write till 200 id's. can i use for loop here ? like this
for(i=0;i<200;i++){
"areas": [ {"id": i}]
}
this is my function
var continentsDataProvider = {
"map": "continentsLow",
"areas": [ {
"id": 1,
}, {
"id": "2",
}, {
}, {
"id": "3",
}, {
}, {
"id": "4",
}, {
}, {
"id": "5",
}, {
} ]
};
Yes you can, simply try
If your object is
var continentsDataProvider = {
"map": "continentsLow",
"areas": []
};
Run your for-loop as
for(var i=1;i<=200;i++){
continentsDataProvider.areas.push( {"id": i});
}
You can use a for loop to create the objects or you can use (the much nicer) Array.from:
var continentsDataProvider = {
"map": "continentsLow",
"areas": Array.from({ length: 200 }, function(k, v) { return { id: v + 1}; })
};
console.log(continentsDataProvider);
As noted by Emil S. Jørgensen in the comments - Array.from is not supported by internet explorer so you'll need to polyfill it (polyfill code).
I know there are plenty of questions about iterating through JSON objects but I haven't found one that quite relates to my exact problem. This is the JSON that I'm trying to iterate through:
psinsights = {
"kind": "pagespeedonline#result",
"id": "/speed/pagespeed",
"responseCode": 200,
"title": "PageSpeed Home",
"score": 90,
"pageStats": {
"numberResources": 22,
"numberHosts": 7,
"totalRequestBytes": "2761",
"numberStaticResources": 16,
"htmlResponseBytes": "91981",
"cssResponseBytes": "37728",
"imageResponseBytes": "13909",
"javascriptResponseBytes": "247214",
"otherResponseBytes": "8804",
"numberJsResources": 6,
"numberCssResources": 2
},
"formattedResults": {
"locale": "en_US",
"ruleResults": {
"AvoidBadRequests": {
"localizedRuleName": "Avoid bad requests",
"ruleImpact": 0.0
},
"MinifyJavaScript": {
"localizedRuleName": "Minify JavaScript",
"ruleImpact": 0.1417,
"urlBlocks": [
{
"header": {
"format": "Minifying the following JavaScript resources could reduce their size by $1 ($2% reduction).",
"args": [
{
"type": "BYTES",
"value": "1.3KiB"
},
{
"type": "INT_LITERAL",
"value": "0"
}
]
},
"urls": [
{
"result": {
"format": "Minifying $1 could save $2 ($3% reduction).",
"args": [
{
"type": "URL",
"value": "http://code.google.com/js/codesite_tail.pack.04102009.js"
},
{
"type": "BYTES",
"value": "717B"
},
{
"type": "INT_LITERAL",
"value": "1"
}
]
}
},
{
"result": {
"format": "Minifying $1 could save $2 ($3% reduction).",
"args": [
{
"type": "URL",
"value": "http://www.gmodules.com/ig/proxy?url\u003dhttp%3A%2F%2Fjqueryjs.googlecode.com%2Ffiles%2Fjquery-1.2.6.min.js"
},
{
"type": "BYTES",
"value": "258B"
},
{
"type": "INT_LITERAL",
"value": "0"
}
]
}
}
]
}
]
},
"SpriteImages": {
"localizedRuleName": "Combine images into CSS sprites",
"ruleImpact": 0.0
}
}
},
"version": {
"major": 1,
"minor": 11
}
};
Now, I'm trying to write a function that iterates through all of the ruleResults objects and returns an array of the localizedRuleName properties. According to the JSON, ruleResults has three member objects (AvoidBadRequests, MinifyJavaScript, and SpriteImages). Each of these has a localizedRuleName property I'm trying to access, but when I print out my array, it's blank. Here's how I've written my function:
function ruleList(results) {
var ruleArray = [];
for(var ruleName in results.formattedResults.ruleResults){
ruleArray[counter] = results.formattedResults.ruleResults[ruleName].localizedRuleName;
}
return ruleArray;
}
console.log(ruleList(psinsights));
Can you guys help me get on the right track? I used basically this same method to iterate through the pageStats of the JSON and it worked perfectly. I'm not sure why I can't get it to work with these deeper nested objects and properties.
your problem is not your iteration, but your undefined variable "counter".
Instead of using a counter can use the "push" function:
function ruleList(results) {
var ruleArray = [];
for(var ruleName in results.formattedResults.ruleResults){
ruleArray.push(results.formattedResults.ruleResults[ruleName].localizedRuleName);
}
return ruleArray;
}
fiddle: https://jsfiddle.net/fo9h56gh/
Hope this helps.
you're probably getting a javascript error since counter is not defined. you can try this:
function ruleList(results) {
var ruleArray = [];
var counter = 0;
for(var ruleName in results.formattedResults.ruleResults){
ruleArray[counter] = results.formattedResults.ruleResults[ruleName].localizedRuleName;
counter++;
}
return ruleArray;
}