はじめに
Power Automate
を利用しているときに、
「このJSONのキーだけ抽出したいな」というシーンが良くあります。
キー、バリューで構成されるJSONですが、キー値がサクっと取り出せる方法がわかりませんでした。
JavaScriptでは、Object.keys()
メソッドがあり、Office Scripts
に渡せば実現できますが、
そのためだけにOffice Scripts
を起動するのも気が乗りません。
可能であれば、Office Scripts
を起動せず、データ操作や関数の範囲内で完了させたいところです。
そのような中、下記の記事を見つけ、課題が解決できたことから、コチラの紹介と応用技をシェアしたいと思います。
xml 関数とxpath 関数のコラボレーション
今回学んだ手法は
この二段階で実行することができます。
サンプルのJSONで試す
例えば下記のようなJSONがあったとします。
縦長なので、折りたたみます
samples
{
"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": "ビジョンエレクトロニクス株式会社"
}
}
]
}
これをそのまま作成(Compose)でオブジェクトを設け、xml 関数を使ってみましょう。
アクション '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.'.
エラーが発生してしまいます。
なぜでしょうか。試しに親の階層を増やして、実行してみましょう。
{
"root": @{outputs('Sample_JSON')}
}
こちらを実行すると下記の通りXMLが評価されます。
なるほど。rootを追加しない場合、valuesが複数にまたがってしまうため、最上位の親がわからなくなってしまうからのエラーなのでしょうね。
<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 関数を実行してみましょう。
xpath(outputs('XML_へ加工'), '/root/*')
実行するとroot
以下の要素が、Base64エンコードされた状態で返ってきます。
Base64エンコードされた結果が返ってきます
[
{
"$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=="
}
]
それぞれ下記のような形式で要素が抜き出されます。
<values>
<productID>1</productID>
<productName>ノートパソコン</productName>
<category>電子機器</category>
<price>120000</price>
<stockQuantity>50</stockQuantity>
<supplier>
<supplierID>101</supplierID>
<supplierName>株式会社テックサプライ</supplierName>
</supplier>
</values>
ここから更に選択(Select)アクションを用いて、各要素にxpath 関数を実行します。
@{outputs('xpath関数_でroot以下を取得')}
{
"key" : @{xpath(item(),'name(/*)')}
}
xpath(item(),'name(/*)')
のうち、'name(/*)'
は、ルート要素を取得するためのXPath式
です。
実行すると各エントリのroot
要素が取得できます。
[
{
"key": "values"
},
{
"key": "values"
},
{
"key": "values"
},
{
"key": "values"
},
{
"key": "values"
}
]
ほー。これで親要素、言わばカラム名が取得できるのですね!光明が見えてきました。
JSON配列入力の列名を取得する
ここからが応用編です。
今回は各配列に単一の親を持たせるために、Root の追加
で、rootを明示的に追加しましたが、JSON配列入力の最初の要素から、xml 関数を実行する形式に変えてみましょう。
- outputs('Sample_JSON')
+ first(outputs('Sample_JSON')?['values'])
コチラを実行すると、文字通り最初のエントリからroot
の下に、カラム名に該当する要素が列挙されます。
<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)
- 各要素に
xpath(item(),'name(/*)')
- 各要素に
上記を実行することで、選択(Select)アクションの戻り値に、カラム名を列挙することができました🙌
[
{
"key": "productID"
},
{
"key": "productName"
},
{
"key": "category"
},
{
"key": "price"
},
{
"key": "stockQuantity"
},
{
"key": "supplier"
}
]
可変のJSONに対応できる
これができると嬉しいことは可変のJSON配列入力に対応できることです。
親子関係が崩れると適用できませんが、そちらが担保されている場合、カラムが増えても対処できるようになります。
例えばGPT
でランダムなJSON配列入力を出してもらうといったシーンのあと、テーブル作成に活かすことができます。
プロンプトの例は下記の記事をご覧ください。
度々歯がゆい思いをしていた、Markdownのテーブルのヘッダー、区切り文字も含めて、
Markdownのテーブルの作成をしてみたいと思います。
シナリオ
- GPT-4oを用いてダミーデータを作成する
- 戻り値のJSON配列入力を解析する
- カラムを特定する
- ヘッダーと区切り文字、本文をMarkdownに加工する
- 承認コネクタで表示する
承認コネクタを利用している理由は、Markdownがサポートされていることからです。
今回は 2 から取り組みます。
2. 戻り値のJSON配列入力を解析する
子フローに分ける場合は、こちらからがよろしいかと
JSON の解析を実行するのみですが、Schema
の入力を{}
のみにします。
これにより、オブジェクトとして解析はされますが、JSON Schemaの構造変化に対応します。
3. カラムを特定する
今回解説した内容になります。
挙動の詳細
1.root属性をJSON配列入力に追加
{
"root": @{first(body('Parse_JSON')?['values'])}
}
2.xml 関数でXMLへ加工
@{xml(outputs('Root_の追加'))}
3.xpath 関数でroot以下の要素を取得
@{xpath(outputs('XML_へ加工'), '/root/*')}
4.xpath 関数でカラム名を取得
@{xpath(outputs('XML_へ加工'), '/root/*')}
このアクションでカラム名を取得します。
4. ヘッダーと区切り文字、本文をMarkdownに加工する
ヘッダーと区切り文字
まずは結果を格納する配列を変数を初期化する
で用意します。
データ型はArray
です。
ヘッダー
と本文
で処理を分岐します。
ヘッダーは、各要素を結合するのみです。
@{body('Select_Header')}
|
結合(Join)アクションを|
で実施します。
次にヘッダーの要素数に用いて---
を挿入します。
今回は選択(Select)アクションを用いて、---
を配列に格納しました。
@{body('Select_Header')}
@{string('---')}
上記の---
を格納した配列も、結合(Join)アクションを|
で実施します。
@{body('Header区切り_配列')}
|
本文
Apply to each
を使用します。
JSON の解析で取得した配列に該当するvaluesで繰り返し処理を実行します。
@{body('Parse_JSON')?['values']}
各行の値は、選択(Select)アクションをテクニカルに使って配列にします。
@{body('Select_Header')}
@{items('Apply_to_each')[item()]}
この部分は、下記のブログが参考になりますので、おすすめいたします。
あとは各要素を|
で結合(Join)し、配列変数に値を追加していきます。
@{body('行_一次元配列')}
|
Apply to each
が完了次第、改行
で結合(Join)を実施します。
@{variables('Array')}
承認コネクタで表示する
作成(Compose)アクションでMarkdownをまとめます。
|@{body('Header_結合')}|
|@{body('Header区切り_結合')}|
|@{body('各要素を改行して結合')}|
承認コネクタはカスタム応答
で、このようにやってみましょう。
結果は・・・!
GPT
からの返答になるので、ムラもありますが、可変の出力に対応して、Markdownのテーブルが作成できました!
見切れていますね
列の要素や文字列が多すぎると見切れます。
まだまともですね。
とりあえずできました!
やったぜ!
おわりに
頭をコネコネするいい体操になりましたね。
テーブル作成は、要件変更の場合、影響範囲の調査が手間なので使っていきたいです。
改めて知見を広めてくださる方に感謝!
読んでくださる皆様にも感謝です🐟