Search code examples
javascriptjqueryjsondynamicobject

Reformat JSON with dynamic object names in jQuery


I have a simple set of JSON that needs to be reformatted since all of the key value pairs are not always output.

{
"result": [
    {
        "category": "Negative Notification",
        "event": "open",
        "result": 2
    },
    {
        "category": "Referral",
        "event": "bounce",
        "result": 1
    },
    {
        "category": "Negative Notification",
        "event": "delivered",
        "result": 34
    },
    {
        "category": "Negative Notification",
        "event": "processed",
        "result": 34
    },
    {
        "category": "Positive Notification",
        "event": "open",
        "result": 42
    },
    {
        "category": "Referral",
        "event": "delivered",
        "result": 17
    },
    {
        "category": "Positive Notification",
        "event": "processed",
        "result": 504
    },
    {
        "category": "Referral",
        "event": "processed",
        "result": 18
    },
    {
        "category": "Positive Notification",
        "event": "delivered",
        "result": 504
    },
    {
        "category": "Negative Notification",
        "event": "bounce",
        "result": 16
    },
    {
        "category": "Positive Notification",
        "event": "bounce",
        "result": 176
    },
    {
        "category": "Referral",
        "event": "open",
        "result": 10
    }
]
}

The problem with the way this is output is depending on weather the data is available or not, accessing the objects by number could create unexpected functionality. The second problem is that is has to be manipulated by javascript, and cannot be manipulated on the server side.

I would like the JSON to be reformatted so that each category is an object (there are currently three, but could be as many as five) has summarized data inside the object. For example:

{
"result": {
    "Negative Notification" : [ 
        {
        "processed":34,
        "delivered":34,
        "bounces":16,
        "opens":2
        }
    ],
    "Positive Notification" : [
        {
        "processed":504,
        "delivered":504,
        "bounces":176,
        "opens":42
        }
    ],
    "Referral" : [
        {
        "processed":18,
        "delivered":17,
        "bounces":1,
        "opens":10
        }
    ]

}
}

How would I pull this off? Simply looping through and naming the objects is leading me nowhere.


Solution

  • As an addition to T.J Crowder's suggestion: the same basic principle, only without relying on an ES5 function, nor requiring a shim. Note: This solution does differ from your "desired output" in one way: rather than having each category reference an array that has only 1 element (an object literal), I just assign it an object literal directly. Your desired format would require you to access the bounced referrals like so: obj.result.Referral[0].bounces, whereas I think it makes more sense if it were obj.result.Referral.bounces, without the array in between.

    //suppose a is the raw JSON data
    var b = {result:{}};//this will become be the object you want
    for (var i=0;i<a.result.length;i++)
    {
        b.result[a.result[i].category] = (function(obj)
        {
            var p, res = {};
            for (p in obj)
            {
                if (p !== 'category' && obj.hasOwnProperty(p))
                {
                    res[p] = obj[p];
                }
            }
            return res;
        }(a.result[i]));
    }
    

    This loops through the array, referenced by a.result, each time using the value of a.result[i].category as a property name for an object that holds the other data.
    The result is:

    console.log(JSON.stringify(b));
    {"result":
        {"Negative Notification":
            {"event":"bounce",
             "result":16},
        "Referral":
            {"event":"open",
             "result":10},
        "Positive Notification":
            {"event":"bounce","result":176}
        }
    }
    

    But really: why not format the data before you send it, if you have access to the code that outputs this data, change that code to better suite your needs.

    Edit:
    In response to your comment, I think what you're actually after is this:

    var b={result{}};
    for (i=0;i<a.result.length;i++)
    {
        b.result[a.result[i].category] = b.result[a.result[i].category] || {};//use existing, or create new object
        b.result[a.result[i].category][a.result[i].event] = a.result[i].result;//add property for event type, assign value
    }
    

    After this code has run, object b looks like this:

    {"result":
        {"Negative Notification":
            {"open":2,
             "delivered":34,
             "processed":34,
             "bounce":16},
          "Referral":
            {"bounce":1,
             "delivered":17,
             "processed":18,
             "open":10},
          "Positive Notification":
             {"open":42,
              "processed":504,
              "delivered":504,
              "bounce":176}
         }
    }
    

    That means that, instead of using b.result.Referral[0].bounce, you can use b.result.Referral.bounce. But even more importantly, there's no need for that result property in the first place:

    var result ={};
    for (i=0;i<a.result.length;i++)
    {
        result[a.result[i].category] = result[a.result[i].category] || {};
        result[a.result[i].category][a.result[i].event] = a.result[i].result;
    }
    console.log(result);
    
    {"Negative Notification":
        {"open":2,
         "delivered":34,
         "processed":34,
         "bounce":16},
      "Referral":
        {"bounce":1,
         "delivered":17,
         "processed":18,
         "open":10},
      "Positive Notification":
         {"open":42,
          "processed":504,
          "delivered":504,
          "bounce":176}
     }