up vote 1 down vote favorite
share [g+] share [fb]

I've got a JSON response that looks like this:

{
    "COLUMNS":["SETTING_NAME","SETTING_VALUE","COLOR"],
    "DATA": [
                ["setting_1",100.0,"yellow"],
                ["setting_2",150.0,"red"],
                ["setting_3",30.0,"green"],
                ["setting_4",11.0,"blue"]
            ]
 }

How do I find the 'color' for the setting 'setting_4'? Acceptable solutions would either be am easy way to access the data, or a function to transform this into an exploded key/value array like

 [
     setting_1_value: '100', 
     setting_1_color: 'yellow', 
     setting_2_value: "150"
     ...
  ]
link|improve this question

Do you want the value for setting_4 because you're arbitrarily picking that one, or because it's the last one/highest setting number? – Tom Aug 29 '11 at 22:40
Totally arbitrary. The actual data set is much larger. – Luke The Obscure Aug 29 '11 at 22:46
feedback

5 Answers

up vote 2 down vote accepted

You can use this code to put the data into the type of data structure that you asked for:

var response = {"COLUMNS":["SETTING_NAME","SETTING_VALUE","COLOR"],
"DATA":[["setting_1",100.0,"yellow"],["setting_2",150.0,"red"],
["setting_3",30.0,"green"],["setting_4",11.0,"blue"]]};

var data = response.DATA;
var columns = response.COLUMNS;
var hash = {}, item, name, i;
var cols = {};

// remember order of columns
for (i = 0; i < columns.length; i++) {
    cols[columns[i]] = i;
}
// fetch data from correct column
for (i = 0; i < data.length; i++) {
    item = data[i];
    name = item[cols["SETTING_NAME"]];
    hash[name + "_value"] = item[cols["SETTING_VALUE"]];
    hash[name + "_color"] = item[cols["COLOR"]];
}
hash.num = data.length;

As you requested, this gives you a data structure like this so you can directly read any value you want:

{
    "setting_1_value":100,
    "setting_1_color":"yellow",
    "setting_2_value":150,
    "setting_2_color":"red",
    "setting_3_value":30,
    "setting_3_color":"green",
    "setting_4_value":11,
    "setting_4_color":"blue",
    "num":4
}

jsFiddle here: http://jsfiddle.net/jfriend00/HZmYN/ that generated this result.

Personally, I would rather use this code to parse it into this type of data structure:

var response = {"COLUMNS":["SETTING_NAME","SETTING_VALUE","COLOR"],
"DATA":[["setting_1",100.0,"yellow"],["setting_2",150.0,"red"],
["setting_3",30.0,"green"],["setting_4",11.0,"blue"]]};

var data = response.DATA;
var columns = response.COLUMNS;
var newData = [], item, obj, i, num, match;

var cols = {};

// remember order of columns
for (i = 0; i < columns.length; i++) {
    cols[columns[i]] = i;
}

for (i = 0; i < data.length; i++) {
    item = data[i];
    obj = {};
    obj.value = item[cols["SETTING_VALUE"]];
    obj.color = item[cols["COLOR"]];
    obj.name = item[cols["SETTING_NAME"]];
    match = obj.name.match(/\d+$/);
    if (match && match.length > 0) {
        obj.settingNumber = parseInt(match[0], 10);
    }
    newData.push(obj);
}

// now sort the array by the number in the name setting
newData.sort(function(a, b) {
    return(a.settingNumber- b.settingNumber);
});

And generates this data structure:

[
  {"value":100,"color":"yellow","name":"setting_1","settingNumber":1},
  {"value":150,"color":"red","name":"setting_2","settingNumber":2},
  {"value":30,"color":"green","name":"setting_3","settingNumber":3},
  {"value":11,"color":"blue","name":"setting_4","settingNumber":4}
]

Illustrated in this jsFiddle: http://jsfiddle.net/jfriend00/A23Jd/.

The reason I prefer this structure, is you can more easily access the "n" settings as an array of objects:

newData[0].color
newData[0].value
newData[0].name
newData[1].color
....

And, it's easier to iterate through the various settings

link|improve this answer
1  
I think the "_value" and "_color" property suffixes are supposed to come from the COLUMNS array. Hardcoding them isn't robust enough. – Tom Aug 29 '11 at 22:54
It's a bit hard to know what the rules are here and what assumptions you can make. It's not practical to make no assumptions about what's in the data, because the OP can't even look for setting_4 if they don't assume there are settings with those names. We could add code to accept any column order, but it seems like you do have to know what column names to look for so perhaps we can assume the column names. – jfriend00 Aug 29 '11 at 23:15
I like both of these examples for quite a few reasons- primarily for how readable they are, especially the way you access the objects afterwards. Thanks! – Luke The Obscure Aug 29 '11 at 23:15
@jfriend - It's possible to do blind data restructuring where the data itself describes the target structure. The OP may still need to know which property names to look for when attempting to use the data elsewhere, but the restructuring algorithm can (and probably should) be robust enough to scale infinitely. And in some cases, the OP won't even need to know that (though I haven't seen much of that outside of internal-facing tools). – Tom Aug 29 '11 at 23:23
@Tom - updated the first piece of code to pull the column order from the COLUMNS array, though since he asked for an output data structure that "knows" what each value is, I assume he knows the columns he's looking for so this code makes that assumption. But, the columns can now be in any order. I'll update the second piece of code now. – jfriend00 Aug 29 '11 at 23:24
show 2 more comments
feedback

You could simply reduce that list of DATA:

DATA.reduce(function (value, item) { if (item[0] === "setting_4") return item[2] })

You could wrap that whole thing into a function for easier use, passing in the "setting_4" part. E.g.

var getColour = function (id) {
  return DATA.reduce(function (value, item) {
    if (item[0] === id) return item[2]
  })
}

UPDATE: you could zip the two lists together, perhaps that would make access easier?

obj['DATA'].map(function (row) {
  return obj['COLUMNS'].reduce(function (memo, columnName, index) {
    memo[columnName] = row[index]
    return memo
  }, {})
})

This will return something like the following:

[{
   COLOR: "yellow",
   SETTING_NAME: "setting_1",
   SETTING_VALUE: 100
}]
link|improve this answer
1  
I love higher order functions like map and reduce, but you should point out that array.reduce is part of the Javascript 1.8 spec, so for incapable browsers, such as IE <= 8, you'll need to include an implementation of reduce. – wsanville Aug 29 '11 at 22:45
1  
Good point. It is easy to implement though, or you could use a library like augment.js – Oliver Nightingale Aug 29 '11 at 22:47
I think you'd want to save the benefits of any work done to translate the original data structure so future access is cheaper. That way we're not necessarily processing the entire data payload for a single value and throwing the rest away. I think using reduce forecloses on any such possibility. – Tom Aug 29 '11 at 22:48
Oh neat, I had never heard of that library before, good find. – wsanville Aug 29 '11 at 22:48
feedback

You can do this with a simple for loop:

var obj = {"COLUMNS":["SETTING_NAME","SETTING_VALUE","COLOR"],
"DATA":[["setting_1",100.0,"yellow"],["setting_2",150.0,"red"],
["setting_3",30.0,"green"],["setting_4",11.0,"blue"]]};

for(var i = 0; i < obj.DATA.length; i++)
{
    var row = obj.DATA[i]
    if (row[0] == 'setting_4')
    {
        console.log(row[2]);
        break;
    }
}

Prints:

blue
link|improve this answer
feedback

Using $.grep will allow you to access the data without mapping them before:

var json={"COLUMNS":["SETTING_NAME","SETTING_VALUE","COLOR"],
          "DATA":[["setting_1",100.0,"yellow"],
                  ["setting_2",150.0,"red"],
                  ["setting_3",30.0,"green"],
                  ["setting_4",11.0,"blue"]]}

alert($.grep(json.DATA, function(item){return(item[0]=='setting_4');})[0][2])//returns 'blue'

//or using the labels provided by COLUMNS:

alert($.grep(json.DATA, 
             function(a){return(a[0]=='setting_4');})[0][$.inArray('COLOR',json.COLUMNS)])
link|improve this answer
You can probably do this without relying on jQuery if you substitute Array.prototype.filter for jQuery.grep & Array.prototype.indexOf for jQuery.inArray – Oliver Nightingale Aug 29 '11 at 23:13
Doesn't this assume a lot about the order of the data? – jfriend00 Aug 29 '11 at 23:17
I better rely on jQuery because both methods are not supported by IE<9 – Dr.Molle Aug 29 '11 at 23:18
@jfriend00: of course, but the 2nd example uses the key from the COLUMNS. I guess COLUMNS are not there for fun, so $.inArray('COLOR',json.COLUMNS) should always return the index of the color, no matter what order. If COLUMNS doesn't give the information where the color is placed and he can't rely on the order there is no logical way to find the color or anything else. – Dr.Molle Aug 29 '11 at 23:21
feedback

A generic algorithm for translating the dataset into a more-easily-addressed structure.

var json = {
    "COLUMNS": [
        "SETTING_NAME",
        "SETTING_VALUE",
        "COLOR"],
    "DATA": [
        ["setting_1",100.0,"yellow"],
        ["setting_2",150.0,"red"],
        ["setting_3",30.0,"green"],
        ["setting_4",11.0,"blue"]
    ]
};

function translateJSON(json) {
    var oHash = {};

    var data = json['DATA'];
    var cols = json['COLUMNS'];

    for(var i = 0, imax = data.length; i < imax; i++) {
        var row = data[i]; // shorthand

        for(var j = 1, jmax = cols.length; j < jmax; j++) {
            var c = cols[j]; // shorthand
            oHash[(row[0] + '_' + c.replace(/[^_]+_/, '')).toLowerCase()] = row[j];
        }
    }

    return oHash;
}

var h = translateJSON(json);
console.log(h['setting_4_color']);

Edit: updated the code. translateJSON will transform the JSON into the data structure you described, for easier property access. If you anticipate needing to access more than one property from the same JSON payload, it will be much more efficient to do a one-time transform before data access than to use something like $.grep, and much less terse than doing the column-name cross-referencing by hand.

That said, I don't think the target data structure you asked for is necessarily the best one. Assuming you can't change the structure of the JSON payload, it would still probably be better to transform that into something like:

data = {
    'setting_1': { 'value': 100.0, 'color': 'yellow' },
    'setting_2': { 'value': 150.0, 'color': 'red' }
    ...
};
link|improve this answer
feedback

Your Answer

 
or
required, but never shown

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