9
\$\begingroup\$

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.

\$\endgroup\$
  • \$\begingroup\$ Are the names literally date 01, etc.? If so, why not simply make "dates & times" an array of time values, and build the string from the index? \$\endgroup\$ – cbojar Aug 25 '14 at 17:24
  • \$\begingroup\$ You should definitely at least be using an array instead of User01, User02, ... \$\endgroup\$ – Schism Aug 25 '14 at 17:24
  • \$\begingroup\$ @cbojar No the names are not literally 'date 01', etc.; they will be actual dates, e.g., 2014.08.25, i.e., sets of date/time data pair for each user \$\endgroup\$ – user1613163 Aug 25 '14 at 17:27
  • \$\begingroup\$ @Schism Thx for the sugg. Pretty sure at some point in dev I had an array of users, however that's a new question as to how to retrieve the data as described. Care to offer a hint? Thx! \$\endgroup\$ – user1613163 Aug 25 '14 at 17:29
  • \$\begingroup\$ @Schism Btw, what I meant was that I'd assumed the users should be an array however I wasn't sure how to navigate for the desired results; would it be something like 'for (var key in myObj.Users){ ...' ? \$\endgroup\$ – user1613163 Aug 25 '14 at 18:03
8
\$\begingroup\$
{ "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("");
\$\endgroup\$
  • \$\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 Aug 25 '14 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 Aug 25 '14 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 Aug 25 '14 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 Aug 25 '14 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 Aug 25 '14 at 22:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.