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"
    }
]

KeyValue がペアになっているオブジェクトの配列です。この形のほうが使いやすそうです。

必要な部分だけを取り出してループ

まずは、整形にはループ処理が必要です。ほしい情報は、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 のマッピング

おそらくここがキモです。KeyValue を値をうまくマッピングし、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 の整形を行ってきました。今回試してみた整形手法は、他の機会にも応用が出来そうに思えました。