SharePoint の REST API が返す JSON はどうしてこうも複雑なのでしょうか。Power Automate にある「SharePoint に HTTP 要求を送信します」アクションで気軽に利用できるのですが、その戻り値の扱いは手軽ではありません。
今回利用とした API は、SharePoint での検索結果を取得する /_api/search/postquery です。これが返す JSON を Power Automate でも扱いやすいように整えてみます。
元の JSON と整形後の JSON
REST API が返してきた JSON の次のような形です。(これは必要なデータ部分だけを抽出して記載しているもので、実際には他にも様々なメタデータがついてくるため、もっともっと複雑です…)
{ "Table":{ "Rows":{ "results":[ { "Cells":{ "results":[ { "Key":"Name1-1", "Value":"Value1-1" }, { "Key":"Name1-2", "Value":"Value1-2" } ] } }, { "Cells":{ "results":[ { "Key":"Name2-1", "Value":"Value2-1" }, { "Key":"Name2-2", "Value":"Value2-2" } ] } }, { "Cells":{ "results":[ { "Key":"Name3-1", "Value":"Value3-1" }, { "Key":"Name3-2", "Value":"Value3-2" } ] } } ] } } }
構造を見ていくと、Table
オブジェクトの中に Rows
オブジェクトがあり、その下には results
配列があります。results
配列には無名のオブジェクトが複数あり、それぞれには Cells
オブジェクトが含まれます。Cells
オブジェクトには results
配列が含まれており、ここにも無名のオブジェクトが複数あります。それらオブジェクトの中には、必要な値の名前を示す Key
と、値そのものである Value
が含まれています。
これだと使い勝手が良くないので、次のような形に整形してみようと思います。
[ { "Name1-1":"Value1-1", "Name1-2":"Value1-2" }, { "Name2-1":"Value2-1", "Name2-2":"Value2-2" }, { "Name3-1":"Value3-1", "Name3-2":"Value3-2" } ]
Key
と Value
がペアになっているオブジェクトの配列です。この形のほうが使いやすそうです。
必要な部分だけを取り出してループ
まずは、整形にはループ処理が必要です。ほしい情報は、Rows
オブジェクトの Results
配列に含まれているので、Apply to each の「以前の手順から出力を選択」には次のように指定します。
outputs('Original').Table.Rows.results
outputs('Original')
は整形前の JSON だと思ってください。要素を順々に繋げて記述し、欲しいオブジェクトを指定しています。
これによってループ内では、次のような単位で配列内のひとつひとつのデータについて処理できます。
{ "Cells":{ "results":[ { "Key":"Name1-1", "Value":"Value1-1" }, { "Key":"Name1-2", "Value":"Value1-2" } ] } }
Key と Value のマッピング
おそらくここがキモです。Key
と Value
を値をうまくマッピングし、Key:Value
の形にする必要があります。これには「選択」アクションが利用できました。
「選択」アクションの「開始」には次のように設定します。
range(0,length(item().Cells.results))
これは、0 から results 配列の要素数分(正確にはインデックスの最大値分)だけ 1 ずつ増える数の配列を意味しています。つまりこの式が評価されると、[0, 1, 2, 3…] といった値が生成されます。例の場合は、それぞれのデータで Cells
内の results
にある項目数は 2 つなので、[0, 1] になります。このあたりはやっていることが少しややこしいですね。
気を取り直して、「マップ」の「キーの入力」には次のように設定します。
items('Apply_to_each').Cells.results[item()].Key
また、「値の入力」には次のように設定します。
items('Apply_to_each').Cells.results[item()].Value
ここでの処理は頭の中でイメージするのが大事です。item()
は、さきほど作成した [0, 1] の値に置き換わります。選択アクションの処理の途中は次のようなイメージです。
[ { "items('Apply_to_each').Cells.results[0].Key":"items('Apply_to_each').Cells.results[0].Value" }, { "items('Apply_to_each').Cells.results[1].Key":"items('Apply_to_each').Cells.results[1].Value" } ]
また、items('Apply_to_each')
は、ループ内で処理されるひとつひとつのデータを指します。そのため例えばループの 1 回目では、items('Apply_to_each').Cells.results[0].Key
は、元の JSON 内の 1 つ目のデータの Key
である Name1-1
になります。このように残りの部分も評価されると、結果として「選択」アクションは次のような値を出力します。
[ { "Name1-1": "Value1-1" }, { "Name1-2": "Value1-2" } ]
だんだんと目的の形に近づいてきました。
配列をまとめてひとつのオブジェクトに変換
目的の形にするために、配列の要素に別れてしまっているそれぞれのペアをまとめて、ひとつのオブジェクトにします。ここでは次のような形で、一度 JSON オブジェクトから文字列に変化したものに対して文字の置換処理を行い、再び JSON オブジェクトに戻すという方法にしました。},{
を ,
に置換する処理です。
first( json( replace( string( body('選択') ), '},{', ',' ) ) )
ここまでで、次のような形にまで整形できました。
{ "Name1-1": "Value1-1", "Name1-2": "Value1-2" }
変数に格納してまとめる
ループ内で処理されるひとつひとつのデータに対しては変換ができたので、それらを変数に入れてまとめていきます。種類がアレイ(配列)の変数を初期化し、Apply to each のループ内で変換したデータを追加していきます。
整形したデータを確認する
ループを抜けたところで変数に格納された値を確認すると、目的のかたちに整形されたデータとなっているのがわかります。
作成したフローの全体像
JSON を整形するために作成したフローの全体像です。ループの中に処理が 3 つあるので、データ数の 3 倍だけ処理にコストがかかります。極端にデータが多い場合には、Power Automate で設けられている 24 時間のアクション実行回数の制限に気を付けたほうが良いかもしれません。
さいごに
今回は SharePoint REST API にある /_api/search/postquery を楽に利用するために JSON の整形を行ってきました。今回試してみた整形手法は、他の機会にも応用が出来そうに思えました。