Flatten a hierarchical JSON object into a table

Recently I had a requirement to take a list of JSON objects that had a hierarchical structure and represent it as a table. For reference, here’s a sample of what the JSON object looked something like. If you want to try on your own, feel free to copy below.

[{
  "ProductVariants": [{
    "Images": [{
      "ImageTypeID": 27,
      "ImageType": "Hi-Res",
      "FileAndPath": "http://path/to/hires-image.png",
      "AlternateText": "",
      "LastUpdated": "2016-06-07T15:38:02.437"
    }, {
      "ImageTypeID": 25,
      "ImageType": "Preview",
      "FileAndPath": "http://path/to/prev-image.png",
      "AlternateText": "",
      "LastUpdated": "2016-06-07T15:38:02.497"
    }],
    "SKU": "1234",
    "UPC": "0-00000-01234-0",
    "Status": "Active",
    "Flavour": "Grapefruit"
  }],
  "Documents": [{
    "DocumentID": 22059,
    "Title": "Liquid-3Grapefruit",
    "DocumentTypeID": 278,
    "FileandPath": "http://path/to/doc-22059.pdf",
    "LastUpdated": "2016-01-06T13:48:27.073"
  }],
  "Attributes": [{
    "Heading": "Positioning line",
    "Description": "A great source of deliciousness"
  }, {
    "Heading": "Preview summary",
    "Description": "Lorem Ipsum dolor Lorem Ipsum dolor Lorem Ipsum dolor Lorem Ipsum dolor "
  }],
  "ID": 4432,
  "PrimaryName": "Super Mega Grapefruit Chewies",
  "Form": "Liquid",
  "Flavour": "Grapefruit",
  "MainImage": "http://path/to/image.png"
}]

This represented one product, but the actual dataset had many more fields, and hundreds of products. As you can see, there are arrays and arrays of arrays. What I was trying to do was to parse the object and represent each product as a table. I would show each key, and then beside it, I would show the value. I didn’t care about the hierarchical nature of the data, I wanted to flatten it into a single table so it could be moved to something like Excel and worked on without too much trouble.

Getting started: Output the attributes

I knew that there’s likely be some recursion that I needed to do, but for starters, I just wanted to build the framework.

So, after I grabbed the JSON data from the API and put it into a variable called products, I set out just to process the basic elements. For this, I could use a basic for loop, but I decided to use  jQuery’s $.each() which will take care of the browser inconsistencies. You could use Plain vanilla javascript and there’s a great article on Medium on how to convert $.each to vanilla JS. I recommend you check that out later. jQuery can become a crutch and sometimes it’s just not needed.

// iterate through all products ( we have only 1 in our data ) 
$.each(products, function(){
  // iterate through the properties
  $.each(this, function(index,value){
    console.log(index + ' : ' + value);
  })
});

Basically all we’re doing here is iterating through the products and then iterating through each property on the root level. As we are just logging to the console, once you open up web inspector, you’ll see the following in the output if you use the sample data:

ProductVariants : [object Object]
Documents : [object Object]
Attributes : [object Object],[object Object]
ID : 4432
PrimaryName : Super Mega Grapefruit Chewies
Form : Liquid
Flavour : Grapefruit
MainImage : http://path/to/image.png

Flatten the Hierarchy! Down With Structure!

So far, so good, but we haven’t flattened anything yet. We want all our data on the same level and we don’t care about where they are on the tree. Right now, [object Object] isn’t all that useful. We want the data inside those objects. Before we go any further, let’s refactor our code to take the view layer ( currently just outputting to the console ) and put it in it’s own function called: makeRow.

function makeRow(item){
  $.each(item, function(title,data){
    console.log(title + " : " + data);
  })
}
// iterate through all products ( we have 1 in our data ) 
$.each(products, function(){
  makeRow(this);
});

We haven’t changed anything, we just shifted things around, but this step will be important as we want to start going deeper down the tree. There are a few ways to do it, including manually calling the function for each property that we know is an object or an array, but rather than go down this path, we’re going to combine the typeof operator and… recursion.

Recursion, Really?

Recursion seems like a complicated scary thing, until you realize that essentially something we deal with quite frequently. It exists quite frequently in nature ( tree branches, for example ) and we use a form of it when travelling places. We may want to travel across the country in a car. That route ( west to east, for example ) has roads within it. Maybe there are 4 different highway you can take. Within each of those roads there are roads, perhaps an express road in the center. Within this express road there may be two lanes. This is the smallest unit that you can travel on by car. A lane could be seen as a road within a road within a road. Recursion, from a computer’s point of view is simply making a decision at each junction and deciding whether we want to go deeper or if we’re at the core yet.

Our structure is the perfect use case for recursion. It allows the computer do all the grunt work of figuring stuff out. We’re just going to make a simple change to our makeRow function:

function makeRow(item){
  $.each(item, function(title,data){
    if( typeof data === 'object' ) showRow(data); 
    else console.log(title + " : " + data);
  });
}

And voila, we now have our complete dataset, flattened:

ImageTypeID : 27
ImageType : Hi-Res
FileAndPath : http://path/to/hires-image.png
AlternateText : 
LastUpdated : 2016-06-07T15:38:02.437
ImageTypeID : 25
ImageType : Preview
FileAndPath : http://path/to/prev-image.png
AlternateText : 
LastUpdated : 2016-06-07T15:38:02.497
SKU : 1234
UPC : 0-00000-01234-0
Status : Active
Flavour : Grapefruit
DocumentID : 22059
Title : Liquid-3Grapefruit
DocumentTypeID : 278
FileandPath : http://path/to/doc-22059.pdf
LastUpdated : 2016-01-06T13:48:27.073
Heading : Positioning line
Description : A great source of deliciousness
Heading : Preview summary
Description : Lorem Ipsum dolor Lorem Ipsum dolor Lorem Ipsum dolor Lor...
ID : 4432
PrimaryName : Super Mega Grapefruit Chewies
Form : Liquid
Flavour : Grapefruit
MainImage : http://path/to/image.png

Finishing up with some tabular touches

Now, it’s just a matter of making a few changes to our makeRow function to build up some table rows and add them to an element on the page:

function makeRow(item){
 var rows = '';
 $.each(item, function(title,data){
   if( typeof data === 'object' ) { 
     rows += '<tr><th>' + title + '</th><td>&darr;</td></tr>';
     rows += makeRow(data); 
   }
   else {
     rows += '<tr><td>' + title + "</td><td>" + data + '</td></tr>';
   }
 });
 return rows;
}
// iterate through all products ( we have 1 in our data ) 
var table = '';
$.each(products, function(){
 table = '<table><tbody>';
 table += makeRow(this);
 table += '</tbody></table>';
});
$('#tbl').html(table);

 

Of course, you can get a lot fancier than this and check for arrays and treat them differently than objects, but for my purposes, this was all I needed. In a future article, I will go into how you can take a list of items ( like products ) and transpose this data horizontally. It’s a little but trickier to do this.

Leave a Reply

Your email address will not be published. Required fields are marked *