GASでスプレッドシートの行を
オブジェクト配列に変換する方法

getValues()が返す二次元配列(行と列の表)を、ヘッダー行をキーにしたオブジェクトの配列に変換すると、row[3]のような列番号指定から解放され、obj["金額"]で読める堅牢なコードになります。変換・絞り込み・書き戻しまで動くコードで解説します。

|対象: GAS / スプレッドシート / データ整形

Table of Contents

列番号指定のコードはなぜ壊れやすいのか

GASでシートを読むと、getValues()は「行の配列、その中に列の配列」という二次元配列を返します。この生の配列のままrow[3]のように列番号で値を取り出すと、次の問題が起きます。

列の挿入で全部ずれる

先頭にA列を1つ足しただけで、row[3]が別の列を指すようになりコードが総崩れになる

何の値か読み取れない

row[5]と書かれていても、実際に何の列なのかコードだけでは分からず、保守しづらい

集計コードが長くなる

列番号を追いながらループを書くため、絞り込みや合計の処理が読みにくくなる

レビューで間違えやすい

0始まりの列番号と1始まりの列番号(getRange)が混在し、オフバイワンのミスが起きやすい

ヘッダー行をキーにしたオブジェクト({ 氏名: "山田", 金額: 1200 }のような、名前と値のまとまり)の配列に変換しておけば、これらの問題をまとめて解決できます。

ヘッダーをキーにオブジェクト配列へ変換する基本コード

1行目をヘッダー(列名)とみなし、2行目以降の各行をヘッダー名をキーにしたオブジェクトへ変換します。getDataRange()(入力のある範囲を丸ごと取る)で一括読み込みし、mapで1行ずつ組み立てます。

// シートの全データを「ヘッダーをキーにしたオブジェクトの配列」に変換する
function sheetToObjects(sheet) {
  const values = sheet.getDataRange().getValues();
  if (values.length < 2) return []; // ヘッダーだけ/空なら空配列

  const headers = values[0].map((h) => String(h).trim());
  const rows = values.slice(1);

  return rows.map((row) => {
    const obj = {};
    headers.forEach((key, i) => {
      obj[key] = row[i];
    });
    return obj;
  });
}

ヘッダーはtrim()で前後の空白を除いておくのがポイントです。ヘッダーセルに気づかない空白があると、キー名がずれて後のobj["金額"]undefinedになる事故を防げます。

列名で読む・書く使い方

変換したあとは、列名でそのまま値を読めます。列の位置を気にする必要がありません。

function example() {
  const sheet = SpreadsheetApp.getActive().getSheetByName("会員");
  const records = sheetToObjects(sheet);

  // 列番号ではなく列名で読める
  records.forEach((r) => {
    Logger.log(r["氏名"] + " / " + r["メール"]);
  });

  Logger.log("件数: " + records.length);
}

書き戻すときは注意が必要です。オブジェクトのキーの並び順とシートの列順は一致する保証がないため、列の並びを配列で明示し、その順に値を組み立ててからsetValues()で一括書き込みします。

// オブジェクトの配列を、指定した列順でシートに書き戻す
function objectsToSheet(sheet, objects, headers) {
  sheet.clearContents();
  sheet.getRange(1, 1, 1, headers.length).setValues([headers]);

  if (objects.length === 0) return;

  // headers の順番どおりに値を並べ替えてから書き込む
  const rows = objects.map((obj) =>
    headers.map((key) => (obj[key] !== undefined ? obj[key] : ""))
  );
  sheet.getRange(2, 1, rows.length, headers.length).setValues(rows);
}

filterとreduceで絞り込み・集計する

オブジェクト配列にしておくと、JavaScriptの配列メソッドがそのまま使えます。filterで条件に合う行だけを残し、reduceで合計を出す、といった処理が数行で書けます。

function summarizeSales() {
  const sheet = SpreadsheetApp.getActive().getSheetByName("売上");
  const records = sheetToObjects(sheet);

  // 「東京」支店の行だけ抽出
  const tokyo = records.filter((r) => r["支店"] === "東京");

  // 金額を合計(Number()で確実に数値化)
  const total = tokyo.reduce((sum, r) => sum + Number(r["金額"]), 0);

  Logger.log("東京支店の件数: " + tokyo.length);
  Logger.log("東京支店の合計金額: " + total + "円");
}

金額の列はNumber()で明示的に数値化しておくと安全です。セルに文字列が混ざっていても、意図しない文字列連結("100" + "200""100200"になる事故)を避けられます。

空行や余分な空白への対処

シートの途中や末尾に空行が混ざると、キーの値がすべて空のオブジェクトが配列に入ってしまいます。 全セルが空の行を除外してから変換すると、後続の処理がきれいになります。

function sheetToObjectsSafe(sheet) {
  const values = sheet.getDataRange().getValues();
  if (values.length < 2) return [];

  const headers = values[0].map((h) => String(h).trim());

  return values
    .slice(1)
    // すべてのセルが空の行は除外する
    .filter((row) => row.some((cell) => String(cell).trim() !== ""))
    .map((row) => {
      const obj = {};
      headers.forEach((key, i) => {
        obj[key] = row[i];
      });
      return obj;
    });
}

row.some(...)は「1つでも中身のあるセルがあれば残す」という判定です。逆に「1列でも空なら不完全な行として除外したい」場合は、everyや特定キーの有無で判定を変えます。

シート行番号を保持して特定セルだけ更新する

オブジェクトに変換すると「これはシートの何行目か」という情報が失われます。処理結果を元の行に書き戻したいときは、 変換時に実際のシート行番号を_rowとして持たせておくと便利です。行番号はヘッダー分の1を足した値になります。

// 実際のシート行番号を _row として持たせる
function sheetToObjectsWithRow(sheet) {
  const values = sheet.getDataRange().getValues();
  if (values.length < 2) return [];

  const headers = values[0].map((h) => String(h).trim());

  return values.slice(1).map((row, i) => {
    const obj = { _row: i + 2 }; // ヘッダー分+1が実際の行番号
    headers.forEach((key, j) => {
      obj[key] = row[j];
    });
    return obj;
  });
}

// 変換したオブジェクトを使って、その行の特定セルだけ更新する
function markProcessed() {
  const sheet = SpreadsheetApp.getActive().getSheetByName("注文");
  const records = sheetToObjectsWithRow(sheet);
  const statusCol = 5; // E列「状態」

  records
    .filter((r) => r["状態"] !== "処理済み")
    .forEach((r) => {
      // 必要な処理をここで実行
      sheet.getRange(r._row, statusCol).setValue("処理済み");
    });
}

これで「未処理の行だけを抽出して、その行のE列に『処理済み』を書き込む」といった、 一覧の読み取りと部分更新を組み合わせた処理が安全に書けます。

実務での注意点

1. ヘッダーの重複と空欄に注意する

同名のヘッダーが2つあると、後の列の値で上書きされて片方が消えます。ヘッダーが空欄の列があると、空文字のキーにまとめられてしまいます。ヘッダー名は一意で、空欄なしにしておくのが前提です。

2. 1行1行のsetValueは避ける

読み取りはgetDataRange().getValues()で一括、書き込みもsetValues()で一括にするのが基本です。ループの中で1セルずつ読み書きすると、行数に比例して遅くなり実行時間制限に達しやすくなります。

3. 日付や表示形式は用途で読み分ける

getValues()は日付をDate型、数値をnumber型で返します。計算にはこの生の値が向きますが、「1,200円」や「2026/07/27」のような見た目どおりの文字列が欲しい場合はgetDisplayValues()を使います。

まとめ

getValues()の二次元配列をヘッダー付きのオブジェクト配列に変換すると、列名でデータを読み書きでき、列の挿入や並べ替えに強いコードになります。 filterやreduceでの絞り込み・集計が短く書け、_rowで行番号を持たせれば部分更新も安全です。 シート連携のGASを書くときの土台になる、覚えておきたい定番パターンです。

よくある質問

列の位置ではなく列名でデータを読めるようになります。row[3]のような列番号指定は列を1つ挿入するだけで全部ずれますが、obj["金額"]なら列順が変わっても壊れません。さらにfilter・map・reduceといった配列メソッドがそのまま使えるため、絞り込みや集計のコードが短く読みやすくなります。

オブジェクトのキーは重複できないため、後から代入された列の値で上書きされます。実務では「金額」と「金額(税込)」のように名前を分けておくのが安全です。どうしても同名になる場合は、キーに列番号を付けるなど一意化する工夫が必要です。

オブジェクトのキーの順番はシートの列順と一致する保証がありません。書き戻すときは列の並びを配列で明示し、その順にvalues配列を組み立ててからsetValues()で一括書き込みします。本文の「オブジェクト配列をシートに書き戻す」のコードがこの形です。

getValues()は日付をDate型、数値をnumber型で返すため、そのままオブジェクトに入ります。金額を計算するときはNumber()で明示的に数値化すると、文字列が混ざっていても安全です。見た目どおりの文字列が欲しい場合はgetValues()ではなくgetDisplayValues()を使います。

できます。変換時に「_row」のようなキーで実際のシート行番号(ヘッダー分を足した値)を持たせておけば、record._rowを使ってgetRange(record._row, 列).setValue()でその行の特定セルだけを更新できます。本文の最後のコードで紹介しています。

GAS開発・業務Webシステムを
相談する。

スプレッドシートのデータ整形・集計・帳票出力・Gmail自動化など、日々の業務を自動化するGAS開発をご相談いただけます。

GAS Webシステム開発を見る