1

How do I parse a JSON like this? This is Keenio Extraction API query data to be fetched into a Google Spreadsheet.

I tried using the following code to parse the returned JSON, but don't know how to get to access the nested objects and arrays.

function myFunction() {
  var URL = "https://api.keen.io/3.0/projects/" + 
    PROJECT_KEY + "/queries/extraction?api_key=" + 
    API_KEY + "&event_collection=" + 
    EVT_COL + "&timezone=" + 
    TIMEZONE + "&latest=" + 
    LATEST + "&property_names.........."

  var response = UrlFetchApp.fetch(URL);
  var json = response.getContentText();
  var rawdata = JSON.parse(response.getContentText());
  Logger.log(rawdata.result);
  var data = [];
  data.push(rawdata.result);
  SpreadsheetApp.getActiveSheet().appendRow(data);
}

The above simple function I wrote to parse this JSON, but I am not able to grab the nested objects and arrays using dot notation.

Another thing I observed when I was using the famous IMPORTJSON code is that the order of the columns used to appear shuffled every time, so the formulas I wrote on the returned data, used to result in reference errors.

{
  "result": [
    {
      "sg_event_id": "92-OndRfTs6fZjNdHWzLBw",
      "timestamp": 1529618395,
      "url": "https://noname.com?utm_campaign=website&utm_source=sendgrid.com&utm_medium=email",
      "ip": "192.168.1.1",
      "event": "click",
      "keen": {
        "timestamp": "2018-06-21T21:59:55.000Z",
        "created_at": "2018-06-21T22:00:28.532Z",
        "id": "555c1f7c5asdf7000167d87b"
      },
      "url_offset": {
        "index": 38,
        "type": "text"
      },
      "sg_message_id": "F5mwV1rESdyKFA_2bn1IEQ.filter0042p3las1-15933-5B2A68E8-36.0",
      "useragent": "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)",
      "email": "no.name@noname.com"
    }, {
      "sg_event_id": "bjMlfsSfRyuXEVy8LndsYA",
      "timestamp": 1529618349,
      "url": "https://noname.com?utm_campaign=website&utm_source=sendgrid.com&utm_medium=email",
      "ip": "192.168.1.1",
      "event": "click",
      "keen": {
        "timestamp": "2018-06-21T21:59:09.000Z",
        "created_at": "2018-06-21T21:59:39.491Z",
        "id": "555c1f7c5asdf7000167d87b"
      },
      "url_offset": {
        "index": 36,
        "type": "text"
      },
      "sg_message_id": "F5mwV1rESdyKFA_2bn1IEQ.filter0042p3las1-15933-5B2A68E8-36.0",
      "useragent": "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)",
      "email": "no.name@noname.com"
    }, {
      "sg_event_id": "fru_s2s1RtueuqBMNoIoTg",
      "timestamp": 1529618255,
      "url": "https://noname.com?utm_campaign=website&utm_source=sendgrid.com&utm_medium=email",
      "ip": "192.168.1.1",
      "event": "click",
      "keen": {
        "timestamp": "2018-06-21T21:57:35.000Z",
        "created_at": "2018-06-21T21:58:20.374Z",
        "id": "555c1f7c5asdf7000167d87b"
      },
      "url_offset": {
        "index": 29,
        "type": "text"
      },
      "sg_message_id": "F5mwV1rESdyKFA_2bn1IEQ.filter0042p3las1-15933-5B2A68E8-36.0",
      "useragent": "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)",
      "email": "no.name@noname.com"
    }
  ]
}
chuckx
  • 6,484
  • 1
  • 22
  • 23
sifar
  • 1,086
  • 1
  • 17
  • 43
  • Please read [Under what circumstances may I add “urgent” or other similar phrases to my question, in order to obtain faster answers?](//meta.stackoverflow.com/q/326569) - the summary is that this is not an ideal way to address volunteers, and is probably counterproductive to obtaining answers. Please refrain from adding this to your questions. – halfer Jun 24 '18 at 10:44
  • @halfer thanks. got it! :) – sifar Jun 24 '18 at 10:45

1 Answers1

1

The JSON data you provided consists of an object with a single property, result. result contains an array of objects, each representing a record with field->value properties.

In your function, you're creating a row array and pushing the entire result array as a single element of the array.

In sheets, a row is represented by an array with one element per cell.

You need to process the result array and convert each element from an object with field->value properties to an array with one field value per element.

Here's a start:

var rawdata = JSON.parse(json);

// Flatten each record.
for (var i = 0; i < rawdata.result.length; i++) {
  rawdata.result[i] = flatten(rawdata.result[i]);  
}

// Extract headers by scanning all results.
var headers = [];
for (var i = 0; i < rawdata.result.length; i ++) {
  for (var field in rawdata.result[i]) {
    if (!rawdata.result[i].hasOwnProperty(field)) continue; 
    if (headers.indexOf(field) > -1) continue;
    headers.push(field);
  }
}
headers.sort();

// Will contain an array of row arrays.
var data = [];
data.push(headers);

// Convert each result object into a row array and append to output.
for (var i = 0; i < rawdata.result.length; i++) {
  var row = [];
  for (var j = 0; j < headers.length; j++) {
    if (!rawdata.result[i].hasOwnProperty(headers[j])) {
      row.push("");
    } else {
      row.push(rawdata.result[i][headers[j]]);
    }
  }
  data.push(row);
}

And here's the implementation of flatten():

function flatten(obj, opt_prefix) {
  if (typeof obj !== 'object' || obj === null) {
    Logger.log(obj);
    throw "Cannot flatten non-object";
  }
  var prefix = opt_prefix ? opt_prefix + '.' :  '';
  var result = {};
  for (var k in obj) {
    if (!obj.hasOwnProperty(k)) continue;
    if (typeof obj[k] === 'object') {
      var nested = flatten(obj[k], k);
      for (var n in nested) {
        if (!nested.hasOwnProperty(n)) continue;
        result[prefix + n] = nested[n];
      }
      continue;
    }
    result[prefix + k] = obj[k];
  }
  return result;
}

This implementation only flattens nested objects. If a nested array is present, it will cause the execution to fail. It works against your sample data, since no nested arrays are present.

Also note that in Javascript objects properties are unordered. If you want to order them, you need to do so in your code, either by sorting (e.g. see the headers.sort() call), predefining the order if the field names are known or a combination of the two.

chuckx
  • 6,484
  • 1
  • 22
  • 23
  • Hi @chuckx tried your code. Logger.log(data); shows the data...I added **SpreadsheetApp.openById("xxxxxxxx").getSheetByName("Sheet1").appendRow(data);** .getting this error **[Ljava.lang.Object;@536ef384 [Ljava.lang.Object;@9aebd75 [Ljava.lang.Object;@7003ecba**, each in 3 columns...... – sifar Jun 25 '18 at 18:22
  • In my example you end up with a 2D array (i.e. a list of rows). [`Sheet.appendRow()`](https://developers.google.com/apps-script/reference/spreadsheet/sheet#appendrowrowcontents) takes a single row. So you either need to loop through the rows and add them one by one or use [`Sheet.getRange(row, column, numRows, numColumns)`](https://developers.google.com/apps-script/reference/spreadsheet/sheet#getrangerow-column-numrows-numcolumns) to get a properly sized range and then call [`Range.setValues(data)`](https://developers.google.com/apps-script/reference/spreadsheet/range#setvaluesvalues). – chuckx Jun 25 '18 at 18:27
  • thanks. this code ** var sht = SpreadsheetApp.openById("xxxxxxxx").getSheetByName("Sheet1"); data = data.map(function(v, i, data){ sht.appendRow(v); }); ** worked now. BTW, there is a **nested Keen object**. How do i parse it so that i get additional columns such as **Keen_id, Keen_timestamp,Keen_created_at** etc. Thinking loud, nested properties could be an object or an array... – sifar Jun 25 '18 at 18:47
  • Also there seems to be a problem with the dates...some are showing as e.g **1529618395** while the ones in the nested Keen object is showing e.g. **timestamp=2018-06-21T21:59:55.000Z**. – sifar Jun 25 '18 at 18:58
  • Regarding the timestamps, those are the values as they are in the JSON. From the first record, `"timestamp": 1529618395` and `"keen": { "timestamp": "2018-06-21T21:59:09.000Z"`. If you want to change them somehow, you need to specify what you're looking for. – chuckx Jun 26 '18 at 05:21
  • I added simple flattening logic to the answer. I only tackled what's necessary to parse the example data, so no array handling functionality. At this point, you should have more than enough info pointing you towards a full solution. – chuckx Jun 26 '18 at 05:35
  • I am getting error `Incorrect range width, was 10 but should be 13` when dumping into sheet using this code `var Sht=SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1"); var rng = Sht.getRange(1, 1, data.length, data[0].length); rng.setValues(data);`. I am getting only 1 row of data excluding headers. I guess it is because some of the fields don't have values for row 2. Is it easier to handle this? – sifar Jun 26 '18 at 14:26
  • Please update your question with the current state of your implementation. Also, it would have helped to mention that the data will have an inconsistent number of fields. – chuckx Jun 26 '18 at 15:28
  • I am running the code which you gave me. Do i add it to my question? Also, i wasn't sure if some of the fields were empty. It was just an observation found after running your code. – sifar Jun 26 '18 at 16:02
  • I don't have enough information to work with. First, my code is a partial example, not a full implemtatiom of the function from your question. Second, my code works with your example data. If the example data is not representative of the actual data, the code probably needs to be modified to account for the differences. Third, my code will extract only the fields that exist in the first record, which is why I need to see your full implementation to see what might be happening. – chuckx Jun 26 '18 at 16:21
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/173841/discussion-between-chuckx-and-sifar786). – chuckx Jun 26 '18 at 19:16