こんにちは、etau です 🤓
Google Apps Script (GAS) で、利用頻度が一番高いオブジェクトといえば、Sheet オブジェクトですが (へーしゃ比)、 かゆいところにもう少しなメソッドや、構造化されていないデータとの戦いなど、の問題点が残ります。
「構造化されていないデータは、Sheet オブジェクトの問題ではないんじゃ?」との反論もお受けつけいたしますが、すでに運用されている手作業を GAS に置き換える場合、この運用部分の変更がむずかしいことがあります。
非構造化データのシートに関しては、入力規則をご提案するなどして、可能な限り構造化データへの変更をお願いしております
そんなわけで、どんなヘッダー行に対しても対応できるようなコンストラクタを持たせてかっこいい独自 Sheet オブジェクトを作っていきましょう!
クラス
それでは、このようなシートがスプレッドシート上に「シート1」という名前である想定で、クラスを設計していきます。
プロパティ
まず、クラスで作るべきは、最小のプロパティを持つコンストラクタ メソッドです。
Sheets (Spreadsheet) サービスの Sheet クラス は当確として、なにを加えるかですが、非構造化データの対応として、ヘッダーの行数とヘッダーとして利用できるタイトル行のインデックスもプロパティとして持たせます。
メンバー種別 | メンバー名 | メンバーの内容 |
---|---|---|
プロパティ | sheet | 対象となる Sheet オブジェクト |
プロパティ | headerRows | ヘッダーの行数 |
プロパティ | headerIndex | ヘッダー行のインデックス (ユニークな値を持つ行) |
class Sheet { /** * シートに関するコンストラクタ * @constructor * @param {SpreadsheetApp.sheet} sheet - 対象となるシート オブジェクト * @param {number} headerRows - ヘッダーの行数 * @param {number} headerIndex - ヘッダー行のインデックス (ユニークなカラム) */ constructor(sheet = SpreadsheetApp.getActiveSheet(), headerRows = 1, headerIndex = 0) { /** @type {SpreadsheetApp.Sheet} */ this.sheet = sheet; /** @type {number} */ this.headerRows = headerRows; /** @type {number} */ this.headerIndex = headerIndex; } }
これで、ヘッダー行とデータ (レコード) 行 を自在に操る準備はできました 🤓
移譲されたメソッド
次に Google Services の Sheet クラスから、独自クラスで必要なメソッドを移譲します。
今回利用するのは getDataRange() メソッドのみです。
/** * Class Sheet から委譲されたメソッド * NOTE: https://developers.google.com/apps-script/reference/spreadsheet/sheet */ getDataRange() { return this.sheet.getDataRange(); }
最後に、独自メソッドを追加していきます。
今回はメソッドも最小限ですが、ここが Sheet クラスのコアになる部分です。
メソッド
追加するメソッドは以下の 4 種類です。
メンバー種別 | メンバー名 | メンバーの内容 |
---|---|---|
メソッド | getDataRangeValues | Sheet オブジェクトの入力のある範囲を取得するメソッド |
メソッド | getHeaders | ヘッダーを取得するメソッド |
メソッド | getHeaderValues | Sheet オブジェクトのヘッダー部分の行を取得するメソッド |
メソッド | getDataValues | ヘッダーを除いたデータ部分のみを所得するメソッド |
/** * シートの値すべて取得するメソッド * @return {Array.<Array.<number|string|boolean|Date>>} シートの値 */ getDataRangeValues() { const dataRangeValues = this.getDataRange().getValues(); return dataRangeValues; } /** * ヘッダーを取得するメソッド * @return {Array.<string>} ヘッダー一覧 */ getHeaders() { const headerValues = this.getHeaderValues(); const headers = headerValues[this.headerIndex]; return headers; } /** * ヘッダー部分を取得するメソッド * @return {Array.<Array.<string>>} ヘッダー部分 */ getHeaderValues() { const values = this.getDataRangeValues(); const headerValues = values.filter((_, i) => i < this.headerRows); return headerValues; } /** * ヘッダー行を除いたレコード部分を取得するメソッド * @return {Array.<Array.<number|string|boolean|Date>>} レコード */ getDataValues() { const values = this.getDataRangeValues(); const dataValues = values.filter((_, i) => i >= this.headerRows); return dataValues; }
使い方
以下のような使い方をしていただくと、便利さを感じていただけるかと思います。
function myFunction() { const ss = SpreadsheetApp.getActiveSpreadsheet(); const sheet = new Sheet(ss.getSheetByName('シート1'), 2, 0); console.log(sheet.getDataRangeValues()); // すべてのシートの値 console.log(sheet.getHeaderValues()); // ヘッダーの 2 行分の値 console.log(sheet.getHeaders()); // ユニークなヘッダー行の値 console.log(sheet.getDataValues()); // データ部分の値 }
おまけ
こちらのブログで紹介した、オブジェクトを作成する関数も、このクラスにメソッドとして組み込むと、こんな感じになります。
/** * レコードをオブジェクト化して取得するメソッド * @return {Array.<Object.<string|boolean|Date>>} */ getAsObjects() { const headers = this.getHeaders(); const records = this.getDataValues(); const objects = records.map(record => Object.fromEntries(record. map((value, i) => [headers[i], value]) )); return objects; }
呼び出し側の関数はこちらです。
function myFunction() { const ss = SpreadsheetApp.getActiveSpreadsheet(); const sheet = new Sheet(ss.getSheetByName('シート1'), 2, 0); console.log(sheet.getAsObjects()); // [ { '日付': Mon May 30 2022 00:00:00 GMT+0900 (Japan Standard Time), '担当': 'etau', ToDo: 'やること 1', 'チェック リスト': true }, ... }
プロパティ名はヘッダー行をそのまま使使ってしまうので、注意が必要ですが、3 行でオブジェクト化できてしまうのは、なかなか魅力的ではないでしょうか?🤓
GitHub リンク
まとめ
まだ第 1 段階ですが Google Services の Sheet クラスを直接使うのではなく、独自の Sheet クラスを作っていくメリットもあわせて感じていただけたらうれしいです🤓
今後は、もっともっと便利なメソッドを追加していきましょう。