Power Automate で key-value Array ⇔ Object (Dictionary) を相互変換して Office Script の呼び出しを節約する

Pocket

本記事は、 Microsoft Power Automate - Qiita Advent Calendar 2024 - Qiita シリーズ3 の 21日目 の記事だ。
…本当は、 シリーズ1 の 19日目 に投稿しようとしてたのだけれど、諸事情で記事更新ができず、代理投稿されてしまったので、改めて投稿し直している。

今回は、 Power Automate に於いて、連想配列の形で情報を持たせた Object と、 key-value Array の間を相互変換する方法を解説する。

Premium コネクタやループ処理などは使用せず、 O(1) オーダーのなるべく少ないステップ数で Object ⇔ Array 配列の相互変換させよう。

昨年の Power Automate Advent Calendar 2023 と同様、ステップ数を節約するテクニックの話題だ。

key-value Array ⇔ Object の変換目的

Array から Object に変換すると、 Power Automate の数式で variables('val-name')['property-name'] とアクセスできる。
このため、ループ処理で一旦統計したデータを後から参照させる場合などで都合が良い。

一方で Object から Array にしておくと、 選択アクション を使って 1ステップ で反復処理が行えるため、処理ステップ数を節約できる。

ほか活躍できる具体例として、Array にすることで Office Script との間での辞書型の情報の受け渡しが可能になることが挙げられる。

Office Script との引数・戻り値の受け渡しには key-value Array がよい

スクリプトのユーザー入力を取得する - Office Scripts#入力の制限 にあるように、 Office Script の入出力に設定する『型』は、リテラル型とそれらによる配列・オブジェクト型に限られる。
TypeScript で言う Indexシグネチャで表現できるディクショナリパターン の型を、 Office Script の main 関数のパラメータに使おうとすると、 Type literal contains invalid type 'Unrecognized' エラーになってしまうのだ。 1

function main(
  workbook: ExcelScript.Workbook,
  arg1: { [key: string]: { prop1: string, prop2: string } }
) { // <- Parameter type error for parameter 'arg1: TypeLiteral': Type literal contains invalid type 'Unrecognized'.
    const val: { [key: string]: { prop1: string, prop2: string } } = {}; // <- ok
}

幸い引数に object 型を設定することはできるが、型情報が綺麗サッパリ消えてしまうので、型アサーションに頼る必要が出てくる。
加えて、それでも返り値には object 型を設定できない。

function main(
  workbook: ExcelScript.Workbook,
  arg1: object
) {
  for (const key of Object.keys(arg1)) {
    const t = arg1[key] as { prop1: string, prop2: string };
  }
  return arg1; // <- Return type error: 'Object' is not supported. Try using an interface or type literal instead
}

つまり、 HTTP 要求トリガーbody('HTTP_要求').inputs.headers のような、任意のプロパティ名のオブジェクトで渡されるような情報を、 Office Script で処理して返しづらい。

このため代わりに、 { key: string, value: TYPE }[] な key-value 配列に変換して受け渡すことで、型情報を維持したまま Office Script との間で辞書型の情報を受け渡せるわけだ。

function main(
  workbook: ExcelScript.Workbook,
  arg1: { key: string, value: { prop1: string, prop2: string } }[]
): { key: string, value: { prop1: string, prop2: string } }[] { // <- ok
  return arg1; // <- ok
}

Power Automate からの Office Script の呼び出しには 3回/10秒 という制限があるので、 Power Automate の同一フロー中に何度も呼び出したり、ましてのフロー内のループ中に呼び出す事はできない。
このため、 key-value Array を引数に Office Script を 1回 呼び出してその中でまとめて処理をし、 Power Automate 側で key-value Array から辞書型のオブジェクトに戻すのが良いだろう。

相互変換の実装

さて、本題の key-value Array ⇔ Object の相互変換。

具体的な例として、以下のようなオブジェクトと、

{
  "key1_2": {
    "subkey1": "あいうえお",
    "subkey2": "\"},{\"'},{'"
  },
  "key ③": "raw-value1",
  "key 4": "raw-value2",
  "key_x0005_": [ "array-value1" ],
  "key-6": [
    "array-&<\\>",
    "array-&<\\>"
  ]
}

以下のような配列を相互変換しよう。

[
  {
    "key": "key1_2",
    "value": {
      "subkey1": "あいうえお",
      "subkey2": "\"},{\"'},{'"
    }
  },
  {
    "key": "key ③",
    "value": "raw-value1"
  },
  {
    "key": "key 4",
    "value": "raw-value2"
  },
  {
    "key": "key_x0005_",
    "value": [
      "array-value1"
    ]
  },
  {
    "key": "key-6",
    "value": [
      "array-&<\\>",
      "array-&<\\>"
    ]
  }
]

Object → key-value Array

まずは、オブジェクトから key-value 配列に変換する操作。

JavaScript でいうところの、以下のような変換だ。

Object.entries(exampleObject).map(([key, value]) => ({ key, value }));

こちらは、 json や xml, xpath を駆使することで、以下のように 選択アクション 1ステップで変換できる。

  1. まず example-object 変数に変換したいオブジェクトを設定しておく。
  2. 選択アクション で以下のように設定する
    • 開始
      • 次の数式を設定
        xpath(xml(json(concat('{"root":{"item":',string(variables('example-object')),'}}'))),'/root/item/*[not(name(.)=name(following::*))]')
    • マップ
      • キー key: 次の数式を設定
        json(slice(string(json(item())), 1, add(1, indexOf(string(json(item())), '":'))))
      • キー value: 次の数式を設定
        variables('example-object')[json(slice(string(json(item())), 1, add(1, indexOf(string(json(item())), '":'))))]

端的に説明すると、 xpath を使ってルート直下にあったプロパティ名の要素(要素名が同名の場合は最後の要素だけ)で 選択アクション を実行することで、元オブジェクトのルートプロパティ名に対する反復処理を行わせる。
それを用いて、 key キーにその要素名を、 value キーに対応するプロパティ値を設定した、オブジェクトの配列を得ているのだ。

開始 の部分で設定している数式の xml 関数 では、以下のような XML に変換される。

<root>
  <item>
    <key1_2>
      <subkey1>あいうえお</subkey1>
      <subkey2>"},{"'},{'</subkey2>
    </key1_2>
    <key_x0020__x2462_>raw-value1</key_x0020__x2462_>
    <key_x3000_4>raw-value2</key_x3000_4>
    <key_x005F_x0005_>array-value1</key_x005F_x0005_>
    <key-6>array-&<\></key-6>
    <key-6>array-&amp;&lt;\&gt;</key-6>
  </item>
</root>

key_x0005_, key-6 のようにプロパティの値が配列の場合の変換のされ方が特徴的だ。
欲しいのはとりあえず各プロパティ名だけなので、 '/root/item/*[not(name(.)=name(following::*))]' の xpath を使って、要素名が同名のものが複数あっても最後の要素だけ選択している。

一方、 選択アクションマップ のほうでは、 item() で渡される XML 要素からプロパティの名前を抽出する。
XML の要素名を得たいだけなら xpath(item(), 'name(/*)') で簡単に手に入るのだが、上述のように XML の要素名は特定のルールでエスケープされてしまっているので、このまま使うと意図した文字列にならない。

string(json(item())) で、要素名がエスケープされた XML から、 {"プロパティ名":"値"} という JSON 文字列に戻る事を活用し、 slice 関数indexOf 関数 を使って、 "プロパティ名" の部分だけ切り出す。
それをさらに json 関数 でパースすれば、エスケープが解除されたプロパティ名の文字列が手に入る。

キー key にはプロパティ名そのまま、キー value には example-object 変数からプロパティ名を指定して改めて取得し直したものをセットする。

結果、以下のような期待通りの出力が得られた。

[
  {
    "key": "key1_2",
    "value": {
      "subkey1": "あいうえお",
      "subkey2": "\"},{\"'},{'"
    }
  },
  {
    "key": "key ③",
    "value": "raw-value1"
  },
  {
    "key": "key 4",
    "value": "raw-value2"
  },
  {
    "key": "key_x0005_",
    "value": [
      "array-value1"
    ]
  },
  {
    "key": "key-6",
    "value": [
      "array-&<\\>",
      "array-&<\\>"
    ]
  }
]

Power Automate で、 xml 関数xpath 関数 を駆使しての要素の集計のやり方については、当ブログの以前の以下の記事でも細かく触れているので、そちらの情報を参照。

key-value Array → Object

お次は、 key-value 配列から辞書型のオブジェクトに変換する操作。

JavaScript でいうところの、以下のような変換だ。

Object.assign({}, ...exampleArray.map(l => ({ [l.key]: l.value })));

こちらは、変換にどうしても2ステップはかかる。

  1. まず example-array 変数に変換したいオブジェクトを設定しておく。
  2. 選択アクション で以下のように設定する
    • 開始
      • example-array 変数をそのまま設定
    • マップ
      • 赤枠あたりにある「テキスト モードに切り替え」をクリック
      • 次の数式を設定
        slice(string(addProperty(json('{}'), item()['key'], item()['value'])),1,-1)
  3. 作成アクション変数の初期化 をはじめ、何でも良いので入力の値に次の数式を設定する
    • json(concat('{',join(body('選択_-_array_to_object_1of2'),','),'}'))
      • body(...) の部分は、動的な値で (2.) の出力をを選択する

流れとしては、 (2.) で JSON のプロパティ名・値の組み合わせを stringify して、前後の {, } を取り除いた文字列の配列を作っておき、

[
  "\"key1_2\":{\"subkey1\":\"あいうえお\",\"subkey2\":\"\\\"},{\\\"'},{'\"}",
  "\"key ③\":\"raw-value1\"",
  "\"key 4\":\"raw-value2\"",
  "\"key_x0005_\":[\"array-value1\"]",
  "\"key-6\":[\"array-&<\\\\>\",\"array-&<\\\\>\"]"
]

(3.) でそれらを join 関数, にて結合し、 {} で括って json 関数 でパースすれば完成だ。

結果、以下のような期待通りの出力が得られた。

{
  "key1_2": {
    "subkey1": "あいうえお",
    "subkey2": "\"},{\"'},{'"
  },
  "key ③": "raw-value1",
  "key 4": "raw-value2",
  "key_x0005_": [
    "array-value1"
  ],
  "key-6": [
    "array-&<\\>",
    "array-&<\\>"
  ]
}

応用

配列から辞書型 Object への変換は、上記を応用すれば元が key-value 型でなくても変換できる。

例えば、 Excel のテーブル内に存在する行を一覧表示 アクション1回で必要な情報を Array としてまとめて取得し、Excel のキー列の文字列を Object のキーとした Object に変換しておけば、 variables('val-name')['キー名']body('作成_-_array_to_object_2of2')['キー名'] と必要な列の情報を簡単に取り出せるぞ。

まとめ

Array ⇔ Object の相互変換をマスターして、 Power Automate フローの実行ステップ数を節約しよう!


  1. main 関数の引数と戻り値以外の場所ではエラーにならない。 

コメントを残す

メールアドレスが公開されることはありません。

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください