Unnest an object with array values using cartesian product

Convert array values from the doGet & doPost event parameters into 2D array when writing them onto a Google Sheet.

Unnest an object with array values using cartesian product
Unnest an object with array values using cartesian product

Backstory

I recently launched Webhooks for Sheets — a Workspace Add-on that would allow you to Instantly create Apps Script-native Webhooks for Google Sheets and while working on that project, I ran into an interesting scenario where I learnt that the query parameters in a webhook can hold the same key values (parameters) multiple times and the structure in which the data is held as part of Google Apps Script Web App, led me to try solving it in a different approach than to simply flatten.

Problem statement

The event parameters* (e.parameters) captured by the doGet & doPost functions in Apps Script are in the following format —

{
    "name": [
      "Sourabh"
    ],
    "email": [
      "code@script.gs",
      "sourabh@choraria.io"
    ],    
    "number": [
      "123",
      "456",
      "789"
    ]
}

*not the be confused by e.parameter which only captures the first / single key<>value pair in the query parameters.

However, this is generally tricky to convert it into the required 2D array format, so it could be appended onto a Google Sheet.

Generally, you may want to try and flatten the JSON but that would produce a result like so —

{
  "name_0": "Sourabh",
  "email_0": "code@script.gs",
  "email_1": "sourabh@choraria.io",
  "number_0": "123",
  "number_1": "456",
  "number_2": "789"
}

If you're wondering what it would look like as part of a webhook GET request's query parameters, here's an example —

https://script.google.com/macros/s/ABCDEFGHIJKL1234567890/exec?name=Sourabh&email=code@script.gs&email=sourabh@choraria.io&number=123&number=456&number=789

Codebase

You can find the entire snippet from my GitHub repository here and I'm also sharing it here for reference —

function cartesian(parameters) {
  let keys = Object.keys(parameters);
  let depth = Object.values(parameters).reduce((product, { length }) => product * length, 1);
  let result = [];
  for (let i = 0; i < depth; i++) {
    let j = i;
    let dict = {};
    for (let key of keys) {
      let size = parameters[key].length;
      dict[key] = parameters[key][j % size];
      j = Math.floor(j / size);
    }
    result.push(dict);
  }
  return result;
}

The above snippet would produce the following output —

[
  {
    "name": "Sourabh",
    "email": "code@script.gs",
    "number": "123"
  },
  {
    "name": "Sourabh",
    "email": "sourabh@choraria.io",
    "number": "123"
  },
  {
    "name": "Sourabh",
    "email": "code@script.gs",
    "number": "456"
  },
  {
    "name": "Sourabh",
    "email": "sourabh@choraria.io",
    "number": "456"
  },
  {
    "name": "Sourabh",
    "email": "code@script.gs",
    "number": "789"
  },
  {
    "name": "Sourabh",
    "email": "sourabh@choraria.io",
    "number": "789"
  }
]

which can then be processed / mapped into a 2D array and then appended onto a Google Sheet.

Credit

As always, I didn't drum-up the solution myself — I'd say perhaps just 40% of it 😉 the result came out as part the community contributions that the good folks at Stack Overflow were so kind enough to offer — thanks to trincot who shared the brilliant solution here.