Power AutomateでJSONのキーを抽出する

# はじめに
`Power Automate`を利用しているときに、
「この*JSON*のキーだけ抽出したいな」というシーンが良くあります。

キー、バリューで構成される*JSON*ですが、キー値がサクっと取り出せる方法がわかりませんでした。
JavaScriptでは、`Object.keys()`メソッドがあり、`Office Scripts`に渡せば実現できますが、
そのためだけに`Office Scripts`を起動するのも気が乗りません。

可能であれば、`Office Scripts`を起動せず、[データ操作](https://learn.microsoft.com/ja-jp/power-automate/data-operations)や[関数](https://learn.microsoft.com/ja-jp/azure/logic-apps/workflow-definition-language-functions-reference)の範囲内で完了させたいところです。
そのような中、下記の[記事](https://shaheer365.blogspot.com/2023/12/extracting-keys-from-json-in-power.html)を見つけ、課題が解決できたことから、コチラの紹介と応用技をシェアしたいと思います。

https://shaheer365.blogspot.com/2023/12/extracting-keys-from-json-in-power.html

# [xml 関数](https://learn.microsoft.com/ja-jp/azure/logic-apps/workflow-definition-language-functions-reference#xml)と[xpath 関数](https://learn.microsoft.com/ja-jp/azure/logic-apps/workflow-definition-language-functions-reference#xpath)のコラボレーション
今回学んだ手法は
- キー値を取得したい*JSON*を[xml 関数](https://learn.microsoft.com/ja-jp/azure/logic-apps/workflow-definition-language-functions-reference#xml)で*XML*に変換する
- [xpath 関数](https://learn.microsoft.com/ja-jp/azure/logic-apps/workflow-definition-language-functions-reference#xpath)で*XPath*クエリを実行する

この二段階で実行することができます。

# サンプルのJSONで試す

例えば下記のような*JSON*があったとします。

:::note warn
縦長なので、折りたたみます
:::

<details><summary>samples</summary>

```json:サンプルデータ
{
    "values": [
        {
            "productID": 1,
            "productName": "ノートパソコン",
            "category": "電子機器",
            "price": 120000,
            "stockQuantity": 50,
            "supplier": {
                "supplierID": 101,
                "supplierName": "株式会社テックサプライ"
            }
        },
        {
            "productID": 2,
            "productName": "デジタルカメラ",
            "category": "電子機器",
            "price": 85000,
            "stockQuantity": 30,
            "supplier": {
                "supplierID": 102,
                "supplierName": "カメラワールド株式会社"
            }
        },
        {
            "productID": 3,
            "productName": "エアフライヤー",
            "category": "家電",
            "price": 15000,
            "stockQuantity": 100,
            "supplier": {
                "supplierID": 103,
                "supplierName": "ホームアプライアンス株式会社"
            }
        },
        {
            "productID": 4,
            "productName": "ランニングシューズ",
            "category": "スポーツ用品",
            "price": 12000,
            "stockQuantity": 200,
            "supplier": {
                "supplierID": 104,
                "supplierName": "アクティブギア株式会社"
            }
        },
        {
            "productID": 5,
            "productName": "LEDテレビ",
            "category": "電子機器",
            "price": 50000,
            "stockQuantity": 40,
            "supplier": {
                "supplierID": 105,
                "supplierName": "ビジョンエレクトロニクス株式会社"
            }
        }
    ]
}
```

</details>

これをそのまま[作成(Compose)](https://learn.microsoft.com/ja-jp/power-automate/data-operations#use-the-compose-action)でオブジェクトを設け、[xml 関数](https://learn.microsoft.com/ja-jp/azure/logic-apps/workflow-definition-language-functions-reference#xml)を使ってみましょう。

```plaintext:Error
アクション 'XML_へ加工' に失敗しました: Unable to process template language expressions in action 'XML_へ加工' inputs at line '0' and column '0': 'The template language function 'xml' parameter is not valid. The provided value cannot be converted to XML: 'This document already has a 'DocumentElement' node.'. Please see https://aka.ms/logicexpressions#xml for usage details.'.
```

エラーが発生してしまいます。

なぜでしょうか。試しに親の階層を増やして、実行してみましょう。

```json
{
  "root": @{outputs('Sample_JSON')}
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/bbd2c0aa-8cbc-0a4a-a183-6049c5e75ec2.png)

こちらを実行すると下記の通り*XML*が評価されます。
なるほど。*root*を追加しない場合、*values*が複数にまたがってしまうため、最上位の親がわからなくなってしまうからのエラーなのでしょうね。

```xml:JSONから変換したXML
<root>
    <values>
        <productID>1</productID>
        <productName>ノートパソコン</productName>
        <category>電子機器</category>
        <price>120000</price>
        <stockQuantity>50</stockQuantity>
        <supplier>
            <supplierID>101</supplierID>
            <supplierName>株式会社テックサプライ</supplierName>
        </supplier>
    </values>
    <values>
        <productID>2</productID>
        <productName>デジタルカメラ</productName>
        <category>電子機器</category>
        <price>85000</price>
        <stockQuantity>30</stockQuantity>
        <supplier>
            <supplierID>102</supplierID>
            <supplierName>カメラワールド株式会社</supplierName>
        </supplier>
    </values>
    <values>
        <productID>3</productID>
        <productName>エアフライヤー</productName>
        <category>家電</category>
        <price>15000</price>
        <stockQuantity>100</stockQuantity>
        <supplier>
            <supplierID>103</supplierID>
            <supplierName>ホームアプライアンス株式会社</supplierName>
        </supplier>
    </values>
    <values>
        <productID>4</productID>
        <productName>ランニングシューズ</productName>
        <category>スポーツ用品</category>
        <price>12000</price>
        <stockQuantity>200</stockQuantity>
        <supplier>
            <supplierID>104</supplierID>
            <supplierName>アクティブギア株式会社</supplierName>
        </supplier>
    </values>
    <values>
        <productID>5</productID>
        <productName>LEDテレビ</productName>
        <category>電子機器</category>
        <price>50000</price>
        <stockQuantity>40</stockQuantity>
        <supplier>
            <supplierID>105</supplierID>
            <supplierName>ビジョンエレクトロニクス株式会社</supplierName>
        </supplier>
    </values>
</root>
```

これから、下記の[xpath 関数](https://learn.microsoft.com/ja-jp/azure/logic-apps/workflow-definition-language-functions-reference#xpath)を実行してみましょう。

```plaintext:xpath関数 でroot以下を取得
xpath(outputs('XML_へ加工'), '/root/*')
```

実行すると`root`以下の要素が、**Base64エンコードされた状態**で返ってきます。

<details><summary>Base64エンコードされた結果が返ってきます</summary>

```json:xpath関数の結果

[
    {
        "$content-type": "application/xml;charset=utf-8",
        "$content": "PHZhbHVlcz4NCiAgPHByb2R1Y3RJRD4xPC9wcm9kdWN0SUQ+DQogIDxwcm9kdWN0TmFtZT7jg47jg7zjg4jjg5Hjgr3jgrPjg7M8L3Byb2R1Y3ROYW1lPg0KICA8Y2F0ZWdvcnk+6Zu75a2Q5qmf5ZmoPC9jYXRlZ29yeT4NCiAgPHByaWNlPjEyMDAwMDwvcHJpY2U+DQogIDxzdG9ja1F1YW50aXR5PjUwPC9zdG9ja1F1YW50aXR5Pg0KICA8c3VwcGxpZXI+DQogICAgPHN1cHBsaWVySUQ+MTAxPC9zdXBwbGllcklEPg0KICAgIDxzdXBwbGllck5hbWU+5qCq5byP5Lya56S+44OG44OD44Kv44K144OX44Op44KkPC9zdXBwbGllck5hbWU+DQogIDwvc3VwcGxpZXI+DQo8L3ZhbHVlcz4="
    },
    {
        "$content-type": "application/xml;charset=utf-8",
        "$content": "PHZhbHVlcz4NCiAgPHByb2R1Y3RJRD4yPC9wcm9kdWN0SUQ+DQogIDxwcm9kdWN0TmFtZT7jg4fjgrjjgr/jg6vjgqvjg6Hjg6k8L3Byb2R1Y3ROYW1lPg0KICA8Y2F0ZWdvcnk+6Zu75a2Q5qmf5ZmoPC9jYXRlZ29yeT4NCiAgPHByaWNlPjg1MDAwPC9wcmljZT4NCiAgPHN0b2NrUXVhbnRpdHk+MzA8L3N0b2NrUXVhbnRpdHk+DQogIDxzdXBwbGllcj4NCiAgICA8c3VwcGxpZXJJRD4xMDI8L3N1cHBsaWVySUQ+DQogICAgPHN1cHBsaWVyTmFtZT7jgqvjg6Hjg6njg6/jg7zjg6vjg4nmoKrlvI/kvJrnpL48L3N1cHBsaWVyTmFtZT4NCiAgPC9zdXBwbGllcj4NCjwvdmFsdWVzPg=="
    },
    {
        "$content-type": "application/xml;charset=utf-8",
        "$content": "PHZhbHVlcz4NCiAgPHByb2R1Y3RJRD4zPC9wcm9kdWN0SUQ+DQogIDxwcm9kdWN0TmFtZT7jgqjjgqLjg5Xjg6njgqTjg6Tjg7w8L3Byb2R1Y3ROYW1lPg0KICA8Y2F0ZWdvcnk+5a626Zu7PC9jYXRlZ29yeT4NCiAgPHByaWNlPjE1MDAwPC9wcmljZT4NCiAgPHN0b2NrUXVhbnRpdHk+MTAwPC9zdG9ja1F1YW50aXR5Pg0KICA8c3VwcGxpZXI+DQogICAgPHN1cHBsaWVySUQ+MTAzPC9zdXBwbGllcklEPg0KICAgIDxzdXBwbGllck5hbWU+44Ob44O844Og44Ki44OX44Op44Kk44Ki44Oz44K55qCq5byP5Lya56S+PC9zdXBwbGllck5hbWU+DQogIDwvc3VwcGxpZXI+DQo8L3ZhbHVlcz4="
    },
    {
        "$content-type": "application/xml;charset=utf-8",
        "$content": "PHZhbHVlcz4NCiAgPHByb2R1Y3RJRD40PC9wcm9kdWN0SUQ+DQogIDxwcm9kdWN0TmFtZT7jg6njg7Pjg4vjg7PjgrDjgrfjg6Xjg7zjgro8L3Byb2R1Y3ROYW1lPg0KICA8Y2F0ZWdvcnk+44K544Od44O844OE55So5ZOBPC9jYXRlZ29yeT4NCiAgPHByaWNlPjEyMDAwPC9wcmljZT4NCiAgPHN0b2NrUXVhbnRpdHk+MjAwPC9zdG9ja1F1YW50aXR5Pg0KICA8c3VwcGxpZXI+DQogICAgPHN1cHBsaWVySUQ+MTA0PC9zdXBwbGllcklEPg0KICAgIDxzdXBwbGllck5hbWU+44Ki44Kv44OG44Kj44OW44Ku44Ki5qCq5byP5Lya56S+PC9zdXBwbGllck5hbWU+DQogIDwvc3VwcGxpZXI+DQo8L3ZhbHVlcz4="
    },
    {
        "$content-type": "application/xml;charset=utf-8",
        "$content": "PHZhbHVlcz4NCiAgPHByb2R1Y3RJRD41PC9wcm9kdWN0SUQ+DQogIDxwcm9kdWN0TmFtZT5MRUTjg4bjg6zjg5M8L3Byb2R1Y3ROYW1lPg0KICA8Y2F0ZWdvcnk+6Zu75a2Q5qmf5ZmoPC9jYXRlZ29yeT4NCiAgPHByaWNlPjUwMDAwPC9wcmljZT4NCiAgPHN0b2NrUXVhbnRpdHk+NDA8L3N0b2NrUXVhbnRpdHk+DQogIDxzdXBwbGllcj4NCiAgICA8c3VwcGxpZXJJRD4xMDU8L3N1cHBsaWVySUQ+DQogICAgPHN1cHBsaWVyTmFtZT7jg5Pjgrjjg6fjg7Pjgqjjg6zjgq/jg4jjg63jg4vjgq/jgrnmoKrlvI/kvJrnpL48L3N1cHBsaWVyTmFtZT4NCiAgPC9zdXBwbGllcj4NCjwvdmFsdWVzPg=="
    }
]
```

</details>

それぞれ下記のような形式で要素が抜き出されます。

```xml:ひとつめの要素
<values>
  <productID>1</productID>
  <productName>ノートパソコン</productName>
  <category>電子機器</category>
  <price>120000</price>
  <stockQuantity>50</stockQuantity>
  <supplier>
    <supplierID>101</supplierID>
    <supplierName>株式会社テックサプライ</supplierName>
  </supplier>
</values>
```

ここから更に[選択(Select)](https://learn.microsoft.com/ja-jp/power-automate/data-operations#use-the-select-action)アクションを用いて、各要素に[xpath 関数](https://learn.microsoft.com/ja-jp/azure/logic-apps/workflow-definition-language-functions-reference#xpath)を実行します。

```plaintext:From
@{outputs('xpath関数_でroot以下を取得')}
```

```Map
{
 "key" : @{xpath(item(),'name(/*)')}
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/715a338f-bd1a-b9db-ba1f-f1b493848c19.png)

`xpath(item(),'name(/*)')`のうち、`'name(/*)'`は、`ルート要素を取得するためのXPath式`です。
実行すると各エントリの`root`要素が取得できます。

```json
[
  {
    "key": "values"
  },
  {
    "key": "values"
  },
  {
    "key": "values"
  },
  {
    "key": "values"
  },
  {
    "key": "values"
  }
]
```

ほー。これで親要素、言わばカラム名が取得できるのですね！光明が見えてきました。

---

# JSON配列入力の列名を取得する
ここからが応用編です。

今回は各配列に単一の親を持たせるために、`Root の追加`で、*root*を明示的に追加しましたが、*JSON配列入力*の最初の要素から、[xml 関数](https://learn.microsoft.com/ja-jp/azure/logic-apps/workflow-definition-language-functions-reference#xml)を実行する形式に変えてみましょう。

```diff_json: Root の追加
- outputs('Sample_JSON')
+ first(outputs('Sample_JSON')?['values'])
```

コチラを実行すると、文字通り最初のエントリから`root`の下に、カラム名に該当する要素が列挙されます。

```xml:xml関数の結果
<root>
    <productID>1</productID>
    <productName>ノートパソコン</productName>
    <category>電子機器</category>
    <price>120000</price>
    <stockQuantity>50</stockQuantity>
    <supplier>
        <supplierID>101</supplierID>
        <supplierName>株式会社テックサプライ</supplierName>
    </supplier>
</root>
```

コチラで前述の
- `xpath(outputs('XML_へ加工'), '/root/*')`
- [選択(Select)](https://learn.microsoft.com/ja-jp/power-automate/data-operations#use-the-select-action)
    - 各要素に`xpath(item(),'name(/*)')`

上記を実行することで、[選択(Select)](https://learn.microsoft.com/ja-jp/power-automate/data-operations#use-the-select-action)アクションの戻り値に、カラム名を列挙することができました🙌

```
[
  {
    "key": "productID"
  },
  {
    "key": "productName"
  },
  {
    "key": "category"
  },
  {
    "key": "price"
  },
  {
    "key": "stockQuantity"
  },
  {
    "key": "supplier"
  }
]
```

# 可変のJSONに対応できる
これができると嬉しいことは可変のJSON配列入力に対応できることです。
親子関係が崩れると適用できませんが、そちらが担保されている場合、カラムが増えても対処できるようになります。

例えば`GPT`で[ランダムなJSON配列入力を出してもらう](https://qiita.com/DEmodoriGatsuO/items/3829122f9033fb634205)といったシーンのあと、テーブル作成に活かすことができます。

プロンプトの例は下記の記事をご覧ください。

https://qiita.com/DEmodoriGatsuO/items/3829122f9033fb634205

度々歯がゆい思いをしていた、*Markdown*のテーブルのヘッダー、区切り文字も含めて、
*Markdown*のテーブルの作成をしてみたいと思います。

# シナリオ
1. *GPT-4o*を用いてダミーデータを作成する
1. 戻り値のJSON配列入力を解析する
1. カラムを特定する
1. ヘッダーと区切り文字、本文を*Markdown*に加工する
1. 承認コネクタで表示する

:::note info
承認コネクタを利用している理由は、[Markdownがサポートされていることからです。](https://learn.microsoft.com/ja-jp/power-automate/approvals-markdown-support)
:::

https://learn.microsoft.com/ja-jp/power-automate/approvals-markdown-support

今回は *2* から取り組みます。

## 2. 戻り値のJSON配列入力を解析する

:::note info
子フローに分ける場合は、こちらからがよろしいかと
:::

*JSON の解析*を実行するのみですが、`Schema`の入力を`{}`のみにします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/0dcf19bd-1eab-f967-7a20-bb360cd53b1c.png)

これにより、オブジェクトとして解析はされますが、*JSON Schema*の構造変化に対応します。

## 3. カラムを特定する
1. *root*属性を*JSON配列入力*に追加
1. [xml 関数](https://learn.microsoft.com/ja-jp/azure/logic-apps/workflow-definition-language-functions-reference#xml)で*XML*へ加工
1. [xpath 関数](https://learn.microsoft.com/ja-jp/azure/logic-apps/workflow-definition-language-functions-reference#xpath)で*root*以下の要素を取得
1. [xpath 関数](https://learn.microsoft.com/ja-jp/azure/logic-apps/workflow-definition-language-functions-reference#xpath)でカラム名を取得

今回解説した内容になります。

<details><summary>挙動の詳細</summary>

1.*root*属性を*JSON配列入力*に追加

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/84cbb0be-7954-9c96-c1d2-d3fea6d341ec.png)

```
{
  "root": @{first(body('Parse_JSON')?['values'])}
}
```

2.[xml 関数](https://learn.microsoft.com/ja-jp/azure/logic-apps/workflow-definition-language-functions-reference#xml)で*XML*へ加工

```
@{xml(outputs('Root_の追加'))}
```

3.[xpath 関数](https://learn.microsoft.com/ja-jp/azure/logic-apps/workflow-definition-language-functions-reference#xpath)で*root*以下の要素を取得

```
@{xpath(outputs('XML_へ加工'), '/root/*')}
```

4.[xpath 関数](https://learn.microsoft.com/ja-jp/azure/logic-apps/workflow-definition-language-functions-reference#xpath)でカラム名を取得

```
@{xpath(outputs('XML_へ加工'), '/root/*')}
```

</details>

このアクションでカラム名を取得します。

## 4. ヘッダーと区切り文字、本文を*Markdown*に加工する 
### ヘッダーと区切り文字
まずは結果を格納する配列を`変数を初期化する`で用意します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/691ac408-5502-eb5f-632b-ceec3c18c0df.png)

データ型は`Array`です。

`ヘッダー`と`本文`で処理を分岐します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/92a427b5-2bb8-39d6-6a41-faa315d8b38d.png)

ヘッダーは、各要素を結合するのみです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/14ba8254-1de5-2142-1a93-e803888c6277.png)

```plaintext:From
@{body('Select_Header')}
```

```plaintext:Join With
|
```

[結合(Join)](https://learn.microsoft.com/ja-jp/power-automate/data-operations#use-the-join-action)アクションを`|`で実施します。

次にヘッダーの要素数に用いて`---`を挿入します。
今回は[選択(Select)](https://learn.microsoft.com/ja-jp/power-automate/data-operations#use-the-select-action)アクションを用いて、`---`を配列に格納しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/84bf21ac-0d4b-ecaa-8e42-463f761eb345.png)

```plaintext:From
@{body('Select_Header')}
```

```plaintext:Join With
@{string('---')}
```

上記の`---`を格納した配列も、[結合(Join)](https://learn.microsoft.com/ja-jp/power-automate/data-operations#use-the-join-action)アクションを`|`で実施します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/b49fc935-e6f6-5a37-a803-8b43c5dd4aad.png)

```plaintext:From
@{body('Header区切り_配列')}
```

```plaintext:Join With
|
```

### 本文
`Apply to each`を使用します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/36490a81-783c-7d23-8df6-bf0bbec10cc7.png)

JSON の解析で取得した配列に該当する*values*で繰り返し処理を実行します。

```
@{body('Parse_JSON')?['values']}
```

各行の値は、[選択(Select)](https://learn.microsoft.com/ja-jp/power-automate/data-operations#use-the-select-action)アクションをテクニカルに使って配列にします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/0cebb566-b2f9-1b70-bebb-94edae106be7.png)

```plaintext:From
@{body('Select_Header')}
```

```plaintext:Map
@{items('Apply_to_each')[item()]}
```

この部分は、[下記のブログ](https://mofumofupower.hatenablog.com/entry/2020/09/05/112357)が参考になりますので、おすすめいたします。

https://mofumofupower.hatenablog.com/entry/2020/09/05/112357

あとは各要素を`|`で[結合(Join)](https://learn.microsoft.com/ja-jp/power-automate/data-operations#use-the-join-action)し、配列変数に値を追加していきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/367a9b15-934e-3e71-ea85-c5a5bf1f664e.png)

```plaintext:From
@{body('行_一次元配列')}
```

```plaintext:Join With
|
```

上記の結果を随時`Array`に格納。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/34656153-7c35-b2d1-74ff-0528ddddcb84.png)

`Apply to each`が完了次第、`改行`で[結合(Join)](https://learn.microsoft.com/ja-jp/power-automate/data-operations#use-the-join-action)を実施します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/da8bff0b-d3ed-ed8e-75d6-00cd4195441a.png)

```plaintext:From
@{variables('Array')}
```

```plaintext:Join With


```

## 承認コネクタで表示する
[作成(Compose)](https://learn.microsoft.com/ja-jp/power-automate/data-operations#use-the-compose-action)アクションでMarkdownをまとめます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/3f50c0de-43b0-2549-8369-4c79f1c5f2de.png)

```plaintext:入力
|@{body('Header_結合')}|
|@{body('Header区切り_結合')}|
|@{body('各要素を改行して結合')}|
```

承認コネクタは`カスタム応答`で、このようにやってみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/3037efe6-3ac7-be1b-f53b-221409554ec3.png)

## 結果は・・・！
`GPT`からの返答になるので、ムラもありますが、可変の出力に対応して、*Markdown*のテーブルが作成できました！

■ Teams
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/db9284b7-9bb6-7d26-a318-5c11aa966b32.png)

見切れていますね

■ Outlook
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/572e72fc-71dc-0685-9035-49197be98277.png)

列の要素や文字列が多すぎると見切れます。

■ Power Automateモバイル
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2734332/d523fce3-0eda-a717-b504-d4ca9fd729ce.png)

まだまともですね。

とりあえずできました！
**やったぜ！**

# おわりに

頭をコネコネするいい体操になりましたね。
テーブル作成は、要件変更の場合、影響範囲の調査が手間なので使っていきたいです。

改めて知見を広めてくださる方に感謝！
読んでくださる皆様にも感謝です🐟