Data Orientation and Conversion¶
Summary¶
The convert module provides pure utilities to transform between four data orientations:
- Column-oriented Map —
Map<string, Cell[]>(primary internal format) - Column-oriented Record —
Record<string, Cell[]>(plain object variant) - Row-oriented —
Map<string, Cell>[](one Map per row) - Array-oriented —
Cell[][](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:
// 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.
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.
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.
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.
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):
// import { arraysToMap } from "gaslamp";
const map = gaslamp.arraysToMap(
[["Alice", 25], ["Bob", 30]], // data rows
["name", "age"] // headers
);
From plain object:
// import { columnsToMap } from "gaslamp";
const map = gaslamp.columnsToMap({
name: ["Alice", "Bob"],
age: [25, 30],
});
From row array:
// 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):
// import { mapToArrays } from "gaslamp";
const arrays = gaslamp.mapToArrays(map); // data rows only
const withHeaders = [Array.from(map.keys()), ...arrays];
To plain object:
// import { mapToColumns } from "gaslamp";
const obj = gaslamp.mapToColumns(map);
To row array:
// 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 becomenullrowsToMap: If a row lacks a key present in other rows, that cell isnullmapToRows/mapToArrays: If columns have different lengths, shorter columns are padded withnull
Example:
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:
- A conversion function to/from the column Map
- Document when to use it
- Update this guide
The architecture doesn't constrain you. It just makes the most common conversions efficient.
Related¶
- Cell and Primitive Types — Understanding what values can appear in each orientation
- BareFrame-First Design — How DataFrames use these orientations internally