見出し画像

GAS × LastPassAPI スプレッドシートにLastPassに格納されている情報を書き込みたい その6 複数階層のオブジェクトから値を取り出す for...in/forEach (完)

前回

データの構造が見えてきたので、うまいことやって繰り返し処理をして全データを取得していくです。

全体像コード例を先に示します。
説明のためにコメント多めです。

全体的な流れとしては下記の通りです。

  1. レスポンスを得る

  2. 整える

  3. スプレッドシートに書き出す

Github


1.レスポンスデータ

1-1.レスポンスを得る

function getSharedFolderDataViaLastPassAPI() {
  const url = 'https://lastpass.com/enterpriseapi.php';
  const scriptProperties = PropertiesService.getScriptProperties();
  const cid = scriptProperties.getProperty('AccountNumber');
  const provhash = scriptProperties.getProperty('ProvisioningHash');

  const options = {
    "method": "POST",
    "header": { "Content-Type": "application/json", },
    "payload": {
      "cid": cid,
      "provhash": provhash,
      "cmd": "getsfdata",//getdetailedsfdata だと 429エラー。 "getuserdata", "getsfdata"は通る
      "data": "all"
    }
  }

  const response = UrlFetchApp.fetch(url, options);
  console.log(`response: ${response}`);
  const obj = JSON.parse(response);
  console.log(obj);
  return obj;
}

1-2. サンプル、テスト用データ

const response = {
  "12345678": {
    "sharedfoldername": "hoge",
    "deleted": false,
    "score": 50,
    "users": [
      {
        "username": "hoge@hoge.com",
        "readonly": "0",
        "give": "1",
        "can_administer": "1",
        "superadmin": false,
        "deletedstatus": "0"
      },
      {
        "username": "fuga@hoge.com",
        "readonly": "0",
        "give": "1",
        "can_administer": "1",
        "superadmin": false,
        "deletedstatus": "0"
      },
      {
        "username": "hege@hoge.com",
        "readonly": "1",
        "give": "0",
        "can_administer": "1",
        "superadmin": false,
        "deletedstatus": "0"
      }
    ]
  },

  "87654321": {
    "sharedfoldername": "hogu",
    "deleted": false,
    "score": 50,
    "users": [
      {
        "username": "hogu@hogu.com",
        "readonly": "0",
        "give": "1",
        "can_administer": "1",
        "superadmin": false,
        "deletedstatus": "0"
      }
    ]
  }
}

2.レスポンスデータを整える

for...in/forEachどちらでもいいです。やり方は色々あるよってことで。
どちらにしても、入れ子構造でデータを取り出しています。

2-1. for...inの例

function forin() {
  const response = getSharedFolderDataObjectViaLastPassAPI();
  const values = []; // 空の配列を用意

  for (const folderId in response) { // Key=folderId  responseの中にあるfolderId分、反復処理する
    // console.log(folderId); // ex: 12345678

    const users = response[folderId]['users'];          // 各オブジェクトの users プロパティ取り出しておく
    // console.log(users);

    for (const user of users) {
      // console.log(user);

      const record = Object.values(response[folderId]); // [ 'hoge', false, 50, [[Object],[Object],[Object],[Object]] ]
      // console.log(record);

      const userInfo = Object.values(user);   // [ 'hoge@hoge.com', '0', '1', '1', false, '0' ]
      // console.log(userInfo);

      record[3] = userInfo;                   //record[3]を userInfoに置き換える[ 'hoge', false, 50, [ 'hoge@hoge.com', '0', '1', '1', false, '0' ]],
      record.unshift(folderId); //配列の先頭にfolderIdを入れる
      values.push(record.flat());//配列にrecordを入れて、flatで一次元配列にする
    }
  }
  console.log(values);
  return  values;
}

2-2. forEachの例

function forEach() {
  const response = getSharedFolderDataObjectViaLastPassAPI();

  const sharedFolderIds = Object.keys(response);// オブジェクトの第1階層のKeyを全て取得。例 12345678
  // console.log(sharedFolderIds);

  const values = [];// 空の配列を用意
  sharedFolderIds.forEach(shareFolderId => { // forEachで各shareFolderIdに対して処理
    const object = response[shareFolderId]; // オブジェクトの第2階層=各shareFolderIdの中身
    // console.log(object);

    const { sharedfoldername, deleted, score, } = object; // 分割代入
    // console.log(sharedfoldername);

    const record = [shareFolderId, sharedfoldername, deleted, score];// 配列として値を格納
    // console.log(record);

    object.users.forEach(user => { // forEachで各user対して処理
      const { username, readonly, give, can_administer, superadmin } = user; // 分割代入
      const tmpRecord = [...record];// スプレッド構文。recordの要素をtmpRecordとして配列の中に展開。
      // console.log(tmpRecord);

      tmpRecord.push(username, readonly, give, can_administer, superadmin);
      values.push(tmpRecord);
    });
  });

  console.log(values);
  return  values;
}


for...in/forEach どちらでも得られる結果は同じです。
どちらもキーを主軸に、その数の分だけ処理をぶん回しているイメージです。
最終的にスプレッドシートに書き出すために、どちらも配列に格納しています。

forEachで、recordに一旦格納して、それを次のforEachでまた展開して一行のレコードにしていくというがアツい。

特にこの辺りは、@etau @Okapie @NAOP4P4 @mkataoka73 などに壁打ちに付き合っていただきながら、あーだこうだ言い合って楽しかったです〜〜!!!感謝!!!

3.スプレッドシートに書き出す

function setValuesOnSheet() {

  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName('シート1');

  const values = forEach();
  console.log(values);
  console.log(values[0]);

  sheet.getRange(2, 1, values.length, values[0].length).clear();
  SpreadsheetApp.flush();
  sheet.getRange(2, 1, values.length, values[0].length).setValues(values);
}

一行目は見出しとして項目名称を入れるだろうという想定。
内容をクリアして、SpreadsheetApp.flush(); で効かせてからset

こんな感じです。

以下は単なる個人的なめも

この先は個人的に気になったところのめもです。


要素数の揺れがある場合

そんな構造を返してくるとは考えにくい、無いと信じたい。
が、もしあったらどうする?うわ〜〜めんどくせ〜〜〜〜

で、これに向き合った猛者がいるんですね。

@Okapie さん、アウトプットお待ちしております❤️

/* responsesの中身だけ以下の4つのモデルケースに
置き換えて出力してみました。userの数は各フォルダ2名に簡略化しています */
  
  const responses = {
  11111111: {// ケース①:追加プロパティなし
    sharedfoldername: '[Delete]fuga_1',
    deleted: true,
    score: 11.1,
    users: [ //userはモデルケースでは各フォルダ2人だけにしています
      {
        "username": "fuga_1_user1_追加なしの場合@lastpass.com",
        "readonly": 0,
        "give": 1,
        "can_administer": 1
      },
      {
        "username": "fuga_1_user2_追加なしの場合@lastpass.com",
        "readonly": 1,
        "give": 0,
        "can_administer": 0
      }
    ]
  },
  22222222: { // ケース②:userの中に追加プロパティがある場合
    sharedfoldername: 'fuga_2',
    deleted: false,
    score: 22.2,
    users: [
      {
        "username": "fuga_2_user1_user内で追加の場合@lastpass.com",
        "readonly": 0,
        "give": 1,
        "can_administer": 1,
​
        "additionalProp1": false,
        "additionalProp2": 0,
        "additionalProp3": "fuga admin",
​
      },
      {
        "username": "fuga_2_user2_user内で追加の場合@lastpass.com",
        "readonly": 1,
        "give": 0,
        "can_administer": 0
      }
    ]
  },
​
  33333333: { // ケース③:usersの後に追加プロパティがある場合
    sharedfoldername: 'fuga_3',
    deleted: false,
    score: 33.3,
    users: [
      {
        "username": "fuga_3_user1_usersの後に追加の場合@lastpass.com",
        "readonly": 0,
        "give": 1,
        "can_administer": 1
      },
      {
        "username": "fuga_3_user2_usersの後に追加の場合@lastpass.com",
        "readonly": 0,
        "give": 1,
        "can_administer": 1
      }
    ],
​
    additionalProp4: false,
    additionalProp5: 0,
    additionalProp6: "fuga admin",
​
  },
​
  44444444: { // ケース④:usersの前に追加プロパティがある場合(ズレが生じる)
    sharedfoldername: 'fuga_4',
    deleted: false,
    score: 44.4,
​
    additionalProp7: false,
    additionalProp8: 0,
    additionalProp9: "fuga admin",
​
    users: [
      {
        "username": "fuga_4_user1_usersの前に追加の場合@lastpass.com",
        "readonly": 0,
        "give": 1,
        "can_administer": 1
      },
      {
        "username": "fuga_4_user2_usersの前に追加の場合@lastpass.com",
        "readonly": 0,
        "give": 1,
        "can_administer": 1
      }
    ]
  },
};
​​
}


あれ、いきなり配列に突っ込んだらあかんかったっけ


いきなりrecordにpushすると、最後は空配列になる。
ここが上手く説明できないので、デバッグしてみます。

1回目(という数え方でいいのか知らんが)


2回目


3回目


4回目

あ、違うわ、これ。values.pushにしないとだ。寝ぼけてんな。
values.pushでこうなるじゃん。

そらそうだよな、こうするとrecordを活かしてないからこうなるわな。
うん、自己解決したな。


は〜〜〜〜、オブジェクト、配列、理解が深まったかな。



#GAS
#API
#パスワード管理
#json
#httpリクエスト
#LastPass
#配列
#オブジェクト





いただいたサポートで、書籍代や勉強費用にしたり、美味しいもの食べたりします!