I'd like to ask if either my JSON data is ill-conceived and whether how I solved it is particularly inefficient/plain stupid/typical.
Given the following JSON data:
{
"User01": {
"dates & times": {
"date 01": "150",
"date 02": "165",
"date 03": "180"
}
},
"User02": {
"dates & times": {
"date 01": "150",
"date 02": "165",
"date 03": "180"
}
},
"User03": {
"dates & times": {
"date 01": "150",
"date 02": "165",
"date 03": "180"
}
}
}
... and wanting to retrieve the dates & times data so it can be rendered, e.g.:
Date 01: Time 01
Date 02: Time 02
Date 03: Time 03
... and which I've achieved thusly:
for (var key in myObj){
if (key == selUser){
for (var date in myObj[key]["dates & times"]){
console.log("Date: " + date + "; " + "weight: " + myObj[key]["dates & times"][date]);
var dtTag = "<dt class=\"date\"><span>Date: </span>" + date + " </dt>";
var ddTag = "<dd class=\"value\"><span>Time: </span>" + myObj[key]["dates & times"][date] + "</dd>";
output.innerHTML += dtTag;
output.innerHTML += ddTag;
}
} // if
} // loop
So, as to the json data: assuming that there could be hundreds of date & time entries, is this even a legit usage of json or should this kind of data be better formatted using arrays, e.g.:
{ "dates & times":
{
"dates": [ "date01", "date02", "date03" ],
"times": [ "150", "165", "180" ]
}
}
And in such a case where I had no control of the JSON data, how could I improve on my solution to iterate for selected user and iterate again through nested object's data?
By the way, this is in context of studying JavaScript, not jQuery or any other library/framework.
1 Answer 1
{ "dates & times": { "dates": [ "date01", "date02", "date03" ], "times": [ "150", "165", "180" ] } }
No, don't do that. You take an explicit relationship in one data structure and make it implicit between two data structures. Instead, do:
{ "dates & times": [
{ "date": "2014-08-24", "time": "150" },
{ "date": "2014-08-24", "time": "165" },
{ "date": "2014-08-24", "time": "180" },
] }
Now the relationship is explicit, and you can iterate over the array more simply and safely than iterating over the keys. (Safely because, if a property is added to Object.prototype
, it will show up as a key.)
for (var key in myObj){ if (key == selUser){ for (var date in myObj[key]["dates & times"]){ console.log("Date: " + date + "; " + "weight: " + myObj[key]["dates & times"][date]); var dtTag = "<dt class=\"date\"><span>Date: </span>" + date + " </dt>"; var ddTag = "<dd class=\"value\"><span>Time: </span>" + myObj[key]["dates & times"][date] + "</dd>"; output.innerHTML += dtTag; output.innerHTML += ddTag; } } // if } // loop
This has a bit more going on than is totally necessary. The outer loop is superfluous, since we only actually do something for one key. The inner loop changes with our new data structure. Also, we can do a bit of reorganization to help performance a bit. We will also change the if
statement into a guard clause to clarify the code path.
// Is this key in the object, and does it have the right property?
if(!myObj[selUser] || !myObj[selUser]["dates & times"]) { // return, throw error, whatever }
output.innerHTML += myObj[selUser]["dates & times"].map(function(el){
console.log("Date: " + el.date + "; Time: " + el.time);
var dtTag = "<dt class=\"date\"><span>Date: </span>" + el.date + "</dt>";
var ddTag = "<dd class=\"value\"><span>Time: </span>" + el.time + "</dd>";
return dtTag + ddTag;
}).join("");
This relies on a newer ES5 feature, so polyfill or replace with equivalent loops if you need to support older browsers (see below). Also, this code does not exactly give the output you specified, FYI.
Without Array#map:
// Is this key in the object, and does it have the right property?
if(!myObj[selUser] || !myObj[selUser]["dates & times"]) { // return, throw error, whatever }
var i, el, dtTag, ddTag;
var listItems = [];
var datesAndTimes = myObj[selUser]["dates & times"];
var datesAndTimesLength = datesAndTimes.length;
for(i = 0; i < datesAndTimesLength; i++) {
el = datesAndTimes[i];
console.log("Date: " + el.date + "; Time: " + el.time);
dtTag = "<dt class=\"date\"><span>Date: </span>" + el.date + "</dt>";
ddTag = "<dd class=\"value\"><span>Time: </span>" + el.time + "</dd>";
listItems.push(dtTag + ddTag);
}
output.innerHTML += listItems.join("");
-
\$\begingroup\$ This is a lot for me to process but I will read it carefully and try it out. Thank you again - much appreciated! \$\endgroup\$user1613163– user16131632014年08月25日 18:08:17 +00:00Commented Aug 25, 2014 at 18:08
-
\$\begingroup\$ @user1613163 It may be easier to compare the version without Array#map to your own first, as it does not abstract some of the complexity away. \$\endgroup\$cbojar– cbojar2014年08月25日 18:11:03 +00:00Commented Aug 25, 2014 at 18:11
-
\$\begingroup\$ re: "The outer loop is superfluous, since we only actually do something for one key" ... the outer loop is how I targeted a specified user (ergo var selUser) ... is there a better method? \$\endgroup\$user1613163– user16131632014年08月25日 19:52:56 +00:00Commented Aug 25, 2014 at 19:52
-
\$\begingroup\$ in fact, does that fact alter any of the other code you suggest? (I'm still studying it), but as I said there's a user selection and the json contains the data for multiple users, so I used the outer loop to specify. Cheers! \$\endgroup\$user1613163– user16131632014年08月25日 20:00:25 +00:00Commented Aug 25, 2014 at 20:00
-
\$\begingroup\$ @user1613163 In JS, we can use objects, like your JSON, as associative arrays, so we don't need to search the keys. We can just index in using the property name. So if
selUser
is "User02",myObj[selUser]
will pull up the data for user 02. \$\endgroup\$cbojar– cbojar2014年08月25日 22:06:52 +00:00Commented Aug 25, 2014 at 22:06
date 01
, etc.? If so, why not simply make"dates & times"
an array of time values, and build the string from the index? \$\endgroup\$User01
,User02
, ... \$\endgroup\$