Arif Ahmed
Arif Ahmed

Reputation: 395

Flatten array into single dynamic object using Javascript

I want to extract object keys into a single array and values in a different array so that I can paste the headers and values into a google sheet.

I want to achieve a dynamic code so that if more fields are pulled in from the API, it can map headers with values.

//API Response Sample.
var data =  [

      {
        "actions": [             
          {
            "action_type": "comment",
            "value": "3"
          },
          {
            "action_type": "like",
            "value": "33"
          },
          {
            "action_type": "link_click",
            "value": "1531"
          },
          {
            "action_type": "mobile_app_install",
            "value": "1049"
          }
        ],
        "spend": "8621.03",
        "date_start": "2017-10-28",
        "date_stop": "2017-11-26"
      }
    ]

So far the below code is fixed not dynamic.

const sheet = SpreadsheetApp.getActiveSheet();

//flatten the objects
var actionObjects = data.map(returnAction)

//get the headers
var headers = Object.keys(actionObjects[0])

//create a 2D array for rows
var actionRows = actionObjects.map(a => headers.map(h => a[h])) 

//write the headers
sheet.getRange(sheet.getLastRow() + 1, 1, 1, headers[0].length).setValues([headers]);

//write the rows
sheet.getRange(sheet.getLastRow() + 1, 1, actionRows.length, actionRows[0].length).setValues(actionRows);
}

function returnAction(data){
  let action = {}
  data.actions.forEach(a => action[a.action_type] = a.value)
  action ['spend'] = data.spend
  action ['date_start'] = data.date_start
  action ['date_stop'] = data.date_stop
  return action
}

Upvotes: 0

Views: 179

Answers (1)

Ping
Ping

Reputation: 911

Object keys into array:

const keys = Object.keys(obj);

Object values into array:

const values = Object.values(obj);

Or both in one go ...

const keys = [];
const values = [];
for (const [key,value] of Object.entries(obj)) {
 keys.push(key);
 values.push(value);
}

If the structure of your object does not change... maybe something like this?

  const action = {};
  data.forEach(obj => {
    for (const [key,value] of Object.entries(obj)) {
      if (Array.isArray(value)) {
        for (const o of value) {
          const a = Object.values(o);
          action[a[0]] = a[1];
        }
      } else action[key] = value;
    }
  })

Try this:

function setResult() {
  const sheet = SpreadsheetApp.getActiveSheet();
  class getResults {
    constructor(arr) {
      this.headers = {};
      this.results = [];
      for (const obj of arr) {
        const actions = {};
        for (const [header,value] of Object.entries(obj)) {
          if (Array.isArray(value)) {
            for (const action of value) {
              const values = Object.values(action);
              actions[values[0]] = values[1];
              this.headers[values[0]] = values[0]
            }
          } else {
            actions[header] = value;
            this.headers[header] = header;
          }
        }
        this.results.push(actions);
      }
    }
    get array() {
      const headers = Object.keys(this.headers);
      const results = [headers];
      for (const action of this.results) {
        results.push(headers.map(header => !!action[header] ? action[header] : ''));
      }
      return results;
    }
  }
  const values = new getResults(data).array;
  sheet.getRange(sheet.getLastRow() + 1, 1, values.length, values[0].length).setValues(values);
}

This is a whole function which takes in the 'data' array-object and split it out onto your spreadesheet.

Explanation: This function is mainly written in Object constructor and Classes.

more about object constructor in JavaScript

more about classes in JavaScript

According to your sample data, there are Objects inside an Array which I believe that each of those Objects are one set of data.

So, the 1st step is using a for ... of loop to work with each data set separately with this line of code for (const obj of arr) {}, this is very much the samething as the var actionObjects = data.map(returnAction) line in your original code. more about for ... of in JavaScript

With each of your data object, it has 2 main structure, ONE is Array: Object: {Key1: Value1, Key2: Value2}, which you want Value1 as header and Value2 as the actual value in the output. TWO is simply Key: Value pairs where you need key as the header and value as the value as output.

To work with the given slice of data set, this line for (const [header,value] of Object.entries(obj)) {} uses another for...of loop together with Object.entries() function to deconstruct the given Object into an 2D Array where each of the array value is one Array containing a pair of [key,value] form the given object. more about Object.entries()

Afterwards, if (Array.isArray(value)) {} will check each value given by the for...of Object.entries() function, if it is an Array, it met the condition ONE, else it is condition TWO.

For condition ONE, you want to use the 1st value of the object as header, and the 2nd value as actual value. for (const action of value) {} iterate the 'values' of the object as an array, and store the values as {value[0]: value[1]} in the object declared before entering the loop fucntion for later use.

For condition TWO, just store it into the same object as condition ONE uses in the format of {key: value}.

At the end of each loop, before going onto the next data object, push the key: value pairs stored in this loop (named as actions) into result array.

Till this step, you alread have an array which looks like this:

Array: [
 Object1: {
  header1: value1,
  header2: value2,
  header3: value3,
  header4: value4,
  ...
 },
 ...
]

The Object this.header {} is declarated to keep track of the length of max header column counts, and get rip of any duplicates (since Object keys cannot be duplicated). This help keep the function working even if some of your data objects may has different headers from the others.

After these loops iterate every object inside your data Array, custom method created with getter function get array() form the final result of all data into a 2D array for the apps script .setValues() function to print it onto your spreadsheet. more about getter

If your main concern is the class and object constructor, here is another version of code without using any of them:

function setResult2() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const headers = {};
  const results = [];
  for (const obj of data) {
    const actions = {};
    for (const [header,value] of Object.entries(obj)) {
      if (Array.isArray(value)) {
        for (const action of value) {
          const values = Object.values(action);
          actions[values[0]] = values[1];
          headers[values[0]] = values[0]
        }
      } else {
        actions[header] = value;
        headers[header] = header;
      }
    }
    results.push(actions);
  }
  const getArray = (results,headers) => {
    const headers_final = Object.keys(headers);
    const results_final = [headers_final];
    for (const action of results) {
      results_final.push(headers_final.map(header => !!action[header] ? action[header] : ''));
    }
    return results_final;
  }
  const values = getArray(results,headers);
  console.log(values)
  sheet.getRange(sheet.getLastRow() + 1, 1, values.length, values[0].length).setValues(values);
}

Upvotes: 2

Related Questions