こんにちは。EXCY TECH Blogの萱場です。
本日は、タイトルの件に関して解説します。
Contents
背景
GASを使ったミニプログラムで、商品データ等をシステムにAPI経由でPOSTする事があるのですが、データの更新などの場合は、データの全項目数が必要なわけではありません。むしろ毎回毎回全項目数を投げていたら、処理も重くなるし、誤った内容でデータを上書きしてしまう危険性もあります。
そのようなときに必要な項目だけで、データの更新をできたら楽だなと思い、やり方を模索しました。
概要

説明を簡単にするために、このようなシンプルなサンプルデータを用意しました。
1行目が、システムのUIにも表示される項目名です。(データベースでいうとA列の品番が、主キーです)
2行目と3行目が、各レコードになります。
このような、データを例えば単価だけ更新したい場合を品番と単価の列だけで、データをポストできれば便利ですよね。
最初は、スプシの内容を取得した後、何列目なら、品番、2列目の項目なら商品名のような値のセットのやり方をしていましたが、それでは、上記のような、項目数が可変的な場合に対応できません。
項目数が可変的でも、ちゃんとどのデータがなんの項目の値なのかをセット出来る仕組みが必要です。
手順
項目名をポストするときの項目名に置換するための相対表を作成する。

こんな感じの読み替えるための相対表を下準備として作ります。(シート名はデータ相対表)
B列は、実際にPOSTするときに使用するキーの名称です。
シート上で用意するのはここまでで、実際のGASのプログラミングを行っていこうと思います。
データ相対表の内容を連想配列にする。
const dataSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('data_相対表');//ヘッダー部分の置換用のシートを取得
const renameData = dataSheet.getDataRange().getValues(); //シートから置換用のデータを配列で取得
const dictIndexData = {}; //置換用の連想配列を用意
// ループ処理で置換用の連想配列を用意。
for(let line of renameData){
dictIndexData[line[0]] = line[1]
}
/*
dictIndexData = { '品番': 'productcode',
'商品名': 'productname',
'規格': 'spec',
'単価': 'price' }
*/
このdictIndexDataは、後で、ポストするデータを作成する際に使用します。
POSTしたいデータを配列で取得
const thisSheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet(); //アクティブなシートの取得
const data = thisSheet.getDataRange().getValues(); // シートからデータの取
/*
data = [ [ '品番', '商品名', '規格', '単価' ],
[ 10001, 'サンプルA', '30cm', 1000 ],
[ 10002, 'サンプルB', '40cm', 2000 ] ]
*/
POSTしたいデータを、配列に格納します。getDataRange().getValues()で、ヘッダーの値も一緒に取得してしまいます。
見出し行だけ取り出す。
const header = data[0]; // 見出し行の抽出
data.shift(); // データから見出し行の削除
見出し行は、次に使用します。
POSTするデータとしては必要ないので、shift()で削除します。
MAP関数を使用し、data内の配列の各要素を、キー: バリューの連想配列に変換する
// 見出し行とデータを連想配列に変換
const dictData = data.map(function(values) {
const obj = {};
values.map(function(value, index) {
obj[header[index]] = value
})
return obj;
})
/*
dictData = [ { '品番': 10001, '商品名': 'サンプルA', '規格': '30cm', '単価': 1000 },
{ '品番': 10002, '商品名': 'サンプルB', '規格': '40cm', '単価': 2000 } ]
*/
map関数は、配列の各要素に関して、コールバック関数を実行して、処理結果を、新しい配列に格納してくれます。(新しい配列とは、ここではdictData)
data自体が二重配列になっており、map関数の中で、更にmap関数が実行されているというすこしだけややこしい状況です。
最初のmap関数のコールバック関数の引数【values】には、dataの各配列が代入されます。
[ 10001, ‘サンプルA’, ’30cm’, 1000 ]
その関数の中で、空のオブジェクト(連想配列)=objを作ります。
次のmap関数のコールバック関数の引数【value】には、取り出した各配列の各要素(10001や、サンプルAなど)が代入され、【index】には、それぞれの要素のインデックス番号が代入されます。
先ほど、作成した【header】を使用し、各【value】に対応する項目名をキーに持つオブジェクトをobj内に追加していき、最後にreturn obj;とすることで、そのobjをdictDataに追加するという流れです。
日本語の項目名をdictIndexDataを使って、置換して、新しいPOSTする用のデータを作成する。
//データ行のを一行ずつ取り出す
for (let i=0; i<dictData.length; i++){
const newData = {}
//見出し行をループ処理して、dictDataからデータを一つずつ取り出してnewDataに追加。
for(index of header){
newData[dictIndexData[index]] = dictData[i][index].toString();
}
console.log(newData);
}
/*
1回目
newData = { productcode: '10001',
productname: 'サンプルA',
spec: '30cm',
price: '1000' }
2回目
newData = { productcode: '10002',
productname: 'サンプルB',
spec: '40cm',
price: '2000' }
*/
最後は、dictDataをループ処理で位置行ずつ処理して行きますが、その中で更にループ処理して、POSTする項目名をキーとして持ち、個々のデータを値として持つオブジェクトを作成します。
最後は、cnosole.logですが、このままAPIに投げる処理に移ることもできますし、1旦、2つのnewDataを値、配列に格納することもできます。
確認
プログラムができたので、以下のようなシートを用意してこのシートで処理を実行してみたいと思います。(GASで実行する場合は、とりあえず今までの処理を何らかのfunction内に入れて実行しています。)

項目は、主キーの品番と更新する単価だけです。
/*
実行結果
1回目
newData = { productcode: '10001', price: '1500' }
2回目
newData = { productcode: '10002', price: '2200' }
*/
うまく必要な項目名だけのオブジェクトができました。
最後に
他にも色々と方法はあると思いますが、この方法だと、テーブルの項目が増えた場合でも、data_相対表の項目を追加するだけなので、プログラムの管理も比較的楽かなと思います。
株式会社デザインXでは、アパレル業界のお客様に向けて、ECサイトや業務基幹システム(ERP)の開発・導入支援を行っております。ご興味をお持ちの方は、是非下記リンクよりお気軽にお問い合わせください。
また、株式会社デザインXでは、ソフトウェアエンジニア・社内SEなど、共に働く仲間を募集しています。テクノロジーでアパレル業界のBtoBビジネスを変えたいという熱意をお持ちの方、お待ちしております。ご興味のある方は、以下のリンクからお問い合わせください!