Skip to content

Data Orientation and Conversion

Summary

The convert module provides pure utilities to transform between four data orientations:

  • Column-oriented MapMap<string, Cell[]> (primary internal format)
  • Column-oriented RecordRecord<string, Cell[]> (plain object variant)
  • Row-orientedMap<string, Cell>[] (one Map per row)
  • Array-orientedCell[][] (2D array without headers)

Each orientation is best for different tasks. The conversion functions let you move freely between them.

Philosophy: Choose the Right Shape for the Job

In standard Google Apps Script, you typically work in one shape:

JavaScript
// GAS gives you a 2D array
const values = sheet.getRange(...).getValues(); // Cell[][]

// If you want to work by column name, you manually iterate
const names = values.map(row => row[0]); // tedious

// If you want to aggregate, you write nested loops
let total = 0;
for (const row of values) {
  total += row[2]; // hope column 2 is the right one!
}

This works, but it's error-prone and repetitive. You're constantly converting shapes in your head.

gaslamp's idea: Different shapes are tools. Use the right tool for the job, and switch when needed.

  • Need to access by column name? Use column-oriented Map
  • Need to aggregate or group? Use column-oriented (all values for one column together)
  • Need to iterate rows? Use row-oriented
  • Need to send to a sheet? Use array-oriented

Switch shapes only when necessary. The conversion functions are pure and efficient.

The Four Orientations

Column-oriented Map: Map<string, Cell[]>

What it is: A map where keys are column names, values are arrays of cells.

JavaScript
const data = new Map([
  ["name", ["Alice", "Bob", "Carol"]],
  ["age", [25, 30, 28]],
]);

When to use:

  • Primary working format inside DataFrames
  • When you need to access/filter/transform by column name
  • When building new columns or dropping existing ones

Why Map, not object? Maps preserve insertion order and handle any key type. Objects are simpler but less flexible.

Column-oriented Record: Record<string, Cell[]>

What it is: A plain JavaScript object with the same structure as Map.

JavaScript
const data = {
  name: ["Alice", "Bob", "Carol"],
  age: [25, 30, 28],
};

When to use:

  • Converting to/from JSON
  • When you need simplicity over flexibility
  • User-facing APIs that expect objects

Row-oriented: Map<string, Cell>[]

What it is: An array of Maps, one per row.

JavaScript
const data = [
  new Map([["name", "Alice"], ["age", 25]]),
  new Map([["name", "Bob"], ["age", 30]]),
];

When to use:

  • Iterating row-by-row
  • When you need to preserve row identity
  • Building up results row by row

Array-oriented: Cell[][]

What it is: A 2D array, no headers.

JavaScript
const data = [
  ["Alice", 25],
  ["Bob", 30],
];

When to use:

  • Reading from or writing to GAS sheets (what getValues() returns)
  • Minimal memory footprint needed
  • Passing to other libraries that expect 2D arrays

Converting Between Orientations

All conversions go through the column-oriented Map as the hub. To convert from one to another, you typically go through it.

Convert from External Formats to Map

From 2D array (GAS sheets):

JavaScript
// import { arraysToMap } from "gaslamp";

const map = gaslamp.arraysToMap(
  [["Alice", 25], ["Bob", 30]], // data rows
  ["name", "age"]                // headers
);

From plain object:

JavaScript
// import { columnsToMap } from "gaslamp";

const map = gaslamp.columnsToMap({
  name: ["Alice", "Bob"],
  age: [25, 30],
});

From row array:

JavaScript
// import { rowsToMap } from "gaslamp";

const rows = [
  new Map([["name", "Alice"], ["age", 25]]),
  new Map([["name", "Bob"], ["age", 30]]),
];
const map = gaslamp.rowsToMap(rows);

Convert from Map to Other Formats

To 2D array (for writing to GAS):

JavaScript
// import { mapToArrays } from "gaslamp";

const arrays = gaslamp.mapToArrays(map); // data rows only
const withHeaders = [Array.from(map.keys()), ...arrays];

To plain object:

JavaScript
// import { mapToColumns } from "gaslamp";

const obj = gaslamp.mapToColumns(map);

To row array:

JavaScript
// import { mapToRows } from "gaslamp";

const rows = gaslamp.mapToRows(map);

Handling Missing Values

What happens when data is jagged (rows have different lengths, or columns have different sizes)?

All conversion functions fill gaps with null:

  • arraysToMap: If a row has fewer columns than headers, missing cells become null
  • rowsToMap: If a row lacks a key present in other rows, that cell is null
  • mapToRows / mapToArrays: If columns have different lengths, shorter columns are padded with null

Example:

JavaScript
const rows = [
  new Map([["a", 1], ["b", 2]]),
  new Map([["a", 3]]), // missing "b"
];
const map = gaslamp.rowsToMap(rows);
map.get("b"); // [2, null]

This behavior is intentional: it makes conversions deterministic and prevents data loss.

Design Principles

Pure functions: All conversion functions have no side effects. Input data is not mutated (arrays and Maps are shallow-copied).

Simple dependencies: The convert module depends only on types, not on other gaslamp modules. You can use it standalone.

Hub architecture: All conversions flow through column-oriented Map. This keeps the codebase maintainable and the logic clear.

Extending This Design

If you need a new orientation (for example, Set<Map> for distributed processing), add:

  1. A conversion function to/from the column Map
  2. Document when to use it
  3. Update this guide

The architecture doesn't constrain you. It just makes the most common conversions efficient.