SharePointリストの選択肢列でカラーバッジ(ピル型)を設定している環境で、Power Appsのキャンバスアプリにも同じバッジを表示したい、というケースはよくあります。SharePointで案件管理・タスク管理をしていて、Power Appsで独自のビュー・フォームを作るとき、ステータスや優先度の選択肢列だけ色なしのテキストになってしまい見た目が崩れる、あの問題です。
Power Appsのコネクタ経由でリストデータを取得しても、色情報は含まれてきません。ではどこに色情報があるのか、どうやって取り出すのか──その仕組みを理解して実装するのが本記事のテーマです。
本記事の実装で、最終的に以下のような表示が実現できます。
はじめに ── 「1対1で色が付いているように見える」の正体
選択肢列の色設定は、GUIのパネルで各選択肢にカラーピッカーを当てるだけの操作です。見た目は「濃い赤→この色」「赤→この色」と1対1の設定に見えます。
しかし内部では、選択肢の色は Column Formatting(列書式) と呼ばれるJSONで管理されています。ユーザーがカラーピッカーを操作するたびにSharePointがこのJSONを自動生成・更新し、CustomFormatter という内部フィールドに保存しています。ユーザーがJSONを直接触ることはなく、その存在を意識する機会もありません。
重要なのは、このJSONの中では選択肢の色が「1対1」ではなく、全選択肢をまとめた1つの条件分岐ツリーとして定義されているという点です。Power Appsのコネクタには色情報が来ない理由もここにあります──色はリストのデータではなく、列の書式設定に埋まっているからです。
⚠️ 前提と制限について
本記事のフローでは、SharePoint REST APIを使って列のフィールド情報を取得し、そのレスポンスに含まれる CustomFormatter フィールドからJSON文字列を読み取っています。REST API自体はMicrosoftが公式に提供しているものですが、CustomFormatter の内部JSON構造はドキュメント化されておらず、SharePointがGUI操作から自動生成する形式に依存しています。
そのため、以下のケースではフローが正常に動作しない可能性があります。
- SharePointのバージョンアップによってColumn FormattingのJSON構造が変更された場合
- 選択肢列の書式設定を手動でカスタマイズしていて、標準とは異なるJSON構造になっている場合
- 列に書式設定がまったく適用されていない場合(
CustomFormatterフィールドが空の場合)
本番環境への導入前には十分な動作確認を行うことを推奨します。
本記事では以下の3点を深掘りします。
- SharePointの列書式JSON(Column Formatting)がどのような構造になっているか、特に
operandsのネスト構造の読み解き方 - そのJSONをPower Automateフローで動的に解析し、選択肢ごとのスタイル情報を取り出す仕組み
- Power AppsでParseJSON/Type関数を使って受け取り、App.Formulasで色変換関数を定義する方法
前提知識
- Power Automateのフロー作成経験(変数、Do until、HTTP要求)
- SharePointリストの基本操作
- Power Appsキャンバスアプリの基本操作
全体アーキテクチャ
Power Apps(キャンバスアプリ)
│ ① Site / List / Column 名を渡してフロー起動
▼
Power Automate(フロー)
│ ② SharePoint REST APIでCustomFormatterを取得
│ ③ JSONを解析してStyleArrayを構築
▼
Power Apps
④ StyleArrayをもとにピルのRGBA色を計算して表示
1. SharePoint列書式JSONの構造を理解する
1-1. GUIの裏側で何が起きているか
選択肢列の色設定は、GUIの編集パネルで各選択肢ごとにカラーピッカーを操作するだけです。ユーザーの感覚としては「濃い赤→この色」「赤→この色」と1対1で設定している操作感です。
しかしこの操作の裏では、SharePointが Column Formatting(列書式) と呼ばれるJSONを自動生成しています。ユーザーがカラーピッカーで色を選ぶたびにこのJSONが書き換わり、CustomFormatter という内部フィールドに保存されます。ユーザーがJSONを直接触ることはなく、その存在を意識する機会もありません。
重要なのは、このJSONの中では選択肢の色が「1対1」ではなく、全選択肢をまとめた1つの条件分岐ツリーとして定義されているという点です。これがPower Apps側での再現を難しくしている根本的な原因です。
選択肢のカラーバッジは「ピル(pill)」と呼ばれる丸みを帯びたラベルで、表示の制御に使われるCSSクラスは次のような命名規則に従っています。
sp-css-backgroundColor-BgDarkRed // 背景色
sp-css-borderColor-WhiteFont // 枠線色
sp-css-color-WhiteFont // 文字色
1-2. JSONの大まかな骨格
{
"elmType": "div",
"style": { "display": "flex", "flex-wrap": "wrap" },
"children": [
{
"elmType": "div",
"style": { "border-radius": "16px", ... },
"attributes": {
"class": { /* ← ここに三項演算子ツリーが入る(PillStyle) */ }
},
"children": [
{
"elmType": "span",
"attributes": {
"class": { /* ← ここにテキスト色クラスが入る(TextStyle) */ }
}
}
]
}
]
}
ピルの背景色は children[0].attributes.class、テキスト色は children[0].children[0].attributes.class に格納されています。
1-3. operands のネスト構造 ── 三項演算子の連鎖
Column FormattingのJSONでは、「条件分岐」を 三項演算子(:演算子)のネスト で表現します。これはJavaScriptの condition ? trueValue : falseValue に相当します。
{
"operator": ":",
"operands": [
{ "operator": "==", "operands": ["[$Column1]", "濃い赤"] }, // 条件
"sp-css-backgroundColor-BgDarkRed sp-css-borderColor-WhiteFont ...", // true時の値
{
"operator": ":", // ← falseの場合はさらに別の三項演算子
"operands": [
{ "operator": "==", "operands": ["[$Column1]", "赤"] },
"sp-css-backgroundColor-BgRed sp-css-borderColor-WhiteFont ...",
{
"operator": ":", // ← さらにネスト(選択肢の数だけ続く)
...
}
]
}
]
}
ポイントは operands[2](false側)が再帰的に同じ構造になっている点です。
選択肢が n 個あると、このツリーは深さ n にネストします。末端(デフォルト値)には文字列が入ります。
この構造を図で表すと次のようになります。
root
├── operands[0] → 条件([$Column1] == "濃い赤")
├── operands[1] → "BgDarkRed ..." ← 一致した場合のCSSクラス
└── operands[2] → 次の三項演算子ノード
├── operands[0] → 条件([$Column1] == "赤")
├── operands[1] → "BgRed ..."
└── operands[2] → さらに次のノード
└── ...(選択肢の数だけ続く)
└── operands[2] → ""(デフォルト値、末端)
各ノードから 選択肢名 と CSSクラス文字列 を取り出す際のパスは以下のとおりです。
| 取得したい値 | パス |
|---|---|
| 選択肢の名前 | operands[0].operands[1] |
| ピルのCSSクラス | operands[1] |
| 次のノード(ループ継続) | operands[2] |
2. Power Automateフローの設計
2-1. トリガー:PowerAppsV2
Power Appsから3つの文字列パラメータを受け取ります。
| パラメータ | 用途 |
|---|---|
Site |
SharePointサイトのURL |
List |
リスト名 |
Column |
列の内部名(InternalName) |
2-2. SharePoint REST APIでCustomFormatterを取得
「SharePointにHTTP要求を送信します」アクションを使用します。
- メソッド: GET
-
URI:
_api/web/lists/GetByTitle('@{triggerBody()?['text_1']}')/Fields/GetByInternalNameOrTitle('@{triggerBody()?['text_2']}')
このAPIレスポンスの d/CustomFormatter フィールドに列書式JSONが文字列として格納されています。
2-3. CustomFormatterをJSONオブジェクトに変換
「作成」アクションで文字列からJSONオブジェクトに変換します。
json(body('SharePoint_に_HTTP_要求を送信します')?['d/CustomFormatter'])
2-4. 変数の初期化
ループ前に4つの変数を用意します。
| 変数名 | 型 | 初期値 | 役割 |
|---|---|---|---|
StyleArray |
配列 | [] |
結果を蓄積する |
PillObject |
オブジェクト | outputs('作成:CustomFormatter')?['children']?[0]?['attributes/class'] |
現在処理中のピルノード |
TxtObject |
オブジェクト | outputs('作成:CustomFormatter')?['children']?[0]?['children']?[0]?['attributes/class'] |
現在処理中のテキストノード |
PillDefault / TxtDefault
|
オブジェクト | {} |
デフォルトスタイル(末端到達時に保存) |
PillObject と TxtObject はそれぞれ ツリーの現在位置を指すポインタ として機能します。
2-5. Do untilループ:ツリーを反復処理
ループの終了条件は次のとおりです。
@empty(outputs('変数の設定:PillObject'))
つまり「operands[2] にノードが存在しなくなった(末端に達した)」時点で終了します。
1ループの処理内容:
① 配列変数に追加
value = PillObject.operands[0].operands[1] ← 選択肢名
PillStyle = PillObject.operands[1] ← ピルCSSクラス
TextStyle = TxtObject.operands[1] ← テキストCSSクラス
② 作成:PillObject ← PillObject.operands[2](次のノードへ進む)
③ 変数の設定:PillObject ← outputs('作成:PillObject')
※ 失敗時(末端で null)→ 変数の設定:PillDefault にキャプチャ
④ 作成:TxtObject ← TxtObject.operands[2](同様に進む)
⑤ 変数の設定:TxtObject ← outputs('作成:TxtObject')
※ 失敗時 → 変数の設定:TxtDefault にキャプチャ
なぜ「失敗時」でデフォルトをキャプチャするのか
末端ノードの operands[2] は文字列("")または存在しません。オブジェクト型の変数にこれを代入しようとするとアクションが失敗します。Power Automateの runAfter で Failed ステータスをトリガーにすることで、明示的なnullチェック式を書かずに末端を検出しています。
2-6. Power Appsへの応答
{
"style": "@{variables('StyleArray')}",
"pilldefault": "@{variables('PillDefault')}",
"txtdefault": "@{variables('TxtDefault')}"
}
StyleArray は次のような配列になっています。
[
{ "value": "濃い赤", "PillStyle": "sp-css-backgroundColor-BgDarkRed sp-css-borderColor-WhiteFont sp-css-color-WhiteFont", "TextStyle": "sp-css-color-WhiteFont" },
{ "value": "赤", "PillStyle": "sp-css-backgroundColor-BgRed ...", "TextStyle": "..." },
...
]
3. Power Appsでのフロー応答の受け取り方
3-1. ParseJSONとTypeでフロー応答を型付きコレクションに変換
フローから返ってくる style は文字列化されたJSON配列です。ParseJSON() の第2引数に Type() でスキーマを渡すことで、UntypedObject を経由せず直接型付きのコレクションとして取得できます。
ClearCollect(
colSPStyle,
ParseJSON(
SharePointListColumnの選択肢スタイルを取得する.Run(
"https://contoso.sharepoint.com/sites/mysite",
"Choices",
"Column1"
).style,
Type([{value: Text, PillStyle: Text, TextStyle: Text}])
)
)
ParseJSON() の第2引数に Type([{...}]) を渡すのがポイントです。配列であることを示す [...] の中にレコード型を定義することで、Power Apps がコレクションとして解釈し、各フィールドも文字列として型推論されます。ForAll や Text() でのキャストは不要です。
4. App.Formulasで色をあらかじめ定義する
4-1. App.FormulasとNamed Formulaとは
App.Formulas は Power Apps キャンバスアプリのアプリレベルの数式定義エリアです。ここに書いた数式は「Named Formula(名前付き数式)」として機能し、アプリ全体のどの画面・コントロールからも参照できます。
変数(Set())との違いは、Named Formulaは値ではなく数式を定義する点です。参照されるたびに評価されるため、常に最新の状態を反映します。
4-2. 色変換関数をApp.Formulasに定義する
App.Formulas に以下の3関数を定義します。CSSクラス名の文字列を受け取り、対応する Color 値を返します。App.Formulas はツリービューの「App」→「Formulas」プロパティから編集できます。
// 背景色
SpBackgroundFromClass(spClass: Text): Color =If(
"sp-css-backgroundColor-BgDarkRed" in spClass, RGBA(164,38,44,1),
"sp-css-backgroundColor-BgRed" in spClass, RGBA(209,52,56,1),
"sp-css-backgroundColor-BgOrange" in spClass, RGBA(202,80,16,1),
"sp-css-backgroundColor-BgBrown" in spClass, RGBA(158,108,0,1),
"sp-css-backgroundColor-BgGreen" in spClass, RGBA(73,130,5,1),
"sp-css-backgroundColor-BgTeal" in spClass, RGBA(3,131,135,1),
"sp-css-backgroundColor-BgDarkGreen" in spClass, RGBA(0,94,80,1),
"sp-css-backgroundColor-BgBlue" in spClass, RGBA(0,120,212,1),
"sp-css-backgroundColor-BgLavender" in spClass, RGBA(79,107,237,1),
"sp-css-backgroundColor-BgDarkBlue" in spClass, RGBA(0,78,140,1),
"sp-css-backgroundColor-BgDarkPurple" in spClass, RGBA(55,50,119,1),
"sp-css-backgroundColor-BgPurple" in spClass, RGBA(114,82,170,1),
"sp-css-backgroundColor-BgDarkPink" in spClass, RGBA(172,83,157,1),
"sp-css-backgroundColor-BgGray" in spClass, RGBA(117,117,117,1),
"sp-css-backgroundColor-BgCoral" in spClass, RGBA(250,187,195,1),
"sp-css-backgroundColor-BgDustRose" in spClass, RGBA(245,204,207,1),
"sp-css-backgroundColor-BgPeach" in spClass, RGBA(254,213,209,1),
"sp-css-backgroundColor-BgGold" in spClass, RGBA(255,235,192,1),
"sp-css-backgroundColor-BgMintGreen" in spClass, RGBA(202,240,204,1),
"sp-css-backgroundColor-BgCyan" in spClass, RGBA(195,248,249,1),
"sp-css-backgroundColor-BgSage" in spClass, RGBA(202,238,233,1),
"sp-css-backgroundColor-BgCornflowerBlue" in spClass, RGBA(212,231,246,1),
"sp-css-backgroundColor-BgLightPurple" in spClass, RGBA(195,202,249,1),
"sp-css-backgroundColor-BgLightBlue" in spClass, RGBA(128,198,255,1),
"sp-css-backgroundColor-BgViolet" in spClass, RGBA(216,216,238,1),
"sp-css-backgroundColor-BgMauve" in spClass, RGBA(230,224,240,1),
"sp-css-backgroundColor-BgLilac" in spClass, RGBA(229,210,227,1),
"sp-css-backgroundColor-BgLightGray" in spClass, RGBA(229,229,229,1),
RGBA(0,0,0,0)
);
// 文字色
SpFontColorFromClass(spClass: Text): Color =If(
"sp-css-color-WhiteFont" in spClass, RGBA(255,255,255,1),
"sp-css-color-CoralFont" in spClass, RGBA(163,14,21,1),
"sp-css-color-DustRoseFont" in spClass, RGBA(170,39,43,1),
"sp-css-color-PeachFont" in spClass, RGBA(160,65,13,1),
"sp-css-color-GoldFont" in spClass, RGBA(143,98,0,1),
"sp-css-color-MintGreenFont" in spClass, RGBA(67,116,6,1),
"sp-css-color-CyanFont" in spClass, RGBA(3,120,124,1),
"sp-css-color-SageFont" in spClass, RGBA(0,117,100,1),
"sp-css-color-CornflowerBlueFont" in spClass, RGBA(0,104,184,1),
"sp-css-color-LightPurpleFont" in spClass, RGBA(24,62,231,1),
"sp-css-color-LightBlueFont" in spClass, RGBA(0,78,140,1),
"sp-css-color-VioletFont" in spClass, RGBA(55,50,119,1),
"sp-css-color-MauveFont" in spClass, RGBA(114,82,170,1),
"sp-css-color-LilacFont" in spClass, RGBA(134,65,123,1),
"sp-css-color-LightGrayFont" in spClass, RGBA(102,102,102,1),
RGBA(50,49,48,1)
);
// 枠線色
SpBorderColorFromClass(spClass: Text): Color =If(
"sp-css-borderColor-WhiteFont" in spClass, RGBA(255,255,255,1),
"sp-css-borderColor-CoralFont" in spClass, RGBA(163,14,21,1),
"sp-css-borderColor-DustRoseFont" in spClass, RGBA(170,39,43,1),
"sp-css-borderColor-PeachFont" in spClass, RGBA(160,65,13,1),
"sp-css-borderColor-GoldFont" in spClass, RGBA(143,98,0,1),
"sp-css-borderColor-MintGreenFont" in spClass, RGBA(67,116,6,1),
"sp-css-borderColor-CyanFont" in spClass, RGBA(3,120,124,1),
"sp-css-borderColor-SageFont" in spClass, RGBA(0,117,100,1),
"sp-css-borderColor-CornflowerBlueFont" in spClass, RGBA(0,104,184,1),
"sp-css-borderColor-LightPurpleFont" in spClass, RGBA(24,62,231,1),
"sp-css-borderColor-LightBlueFont" in spClass, RGBA(0,78,140,1),
"sp-css-borderColor-VioletFont" in spClass, RGBA(55,50,119,1),
"sp-css-borderColor-MauveFont" in spClass, RGBA(114,82,170,1),
"sp-css-borderColor-LilacFont" in spClass, RGBA(134,65,123,1),
"sp-css-borderColor-LightGrayFont" in spClass, RGBA(102,102,102,1),
"sp-css-borderColor-neutralSecondary" in spClass, RGBA(96,94,92,1),
RGBA(96,94,92,1)
);
4-3. コントロールからの参照
ギャラリー内のテキストコントロールでは、LookUp で colSPStyle から現在アイテムの選択肢値に対応するレコードを引いてスタイルを適用します。
- Text1:
Control: ModernText@1.0.0
Properties:
Text: =ThisItem.Column1.Value
Fill: |-
=SpBackgroundFromClass(
LookUp(
colSPStyle,
value = ThisItem.Column1.Value
).PillStyle
)
Color: |-
=SpFontColorFromClass(
LookUp(
colSPStyle,
value = ThisItem.Column1.Value
).TextStyle
)
BorderColor: |-
=SpBorderColorFromClass(
LookUp(
colSPStyle,
value = ThisItem.Column1.Value
).PillStyle
)
BorderStyle: =BorderStyle.Solid
BorderThickness: =2
RadiusBottomLeft: =Min(Self.Height,Self.Width)/2
RadiusBottomRight: =Min(Self.Height,Self.Width)/2
RadiusTopLeft: =Min(Self.Height,Self.Width)/2
RadiusTopRight: =Min(Self.Height,Self.Width)/2
LookUp(colSPStyle, value = ThisItem.Column1.Value) でコレクションから対応レコードを取得し、.PillStyle や .TextStyle でCSSクラス文字列を取り出しています。Radius を Min(Self.Height, Self.Width) / 2 にすることでコントロールのサイズに関わらず常にピル型の丸みになります。
4-4. Named Formulaを使う理由
同じ処理を OnStart で変数に格納する方法もありますが、Named Formulaにはいくつか優位点があります。
| 比較項目 |
OnStart + Set()
|
App.Formulas Named Formula |
|---|---|---|
| 評価タイミング | アプリ起動時に1回 | 参照されるたびに遅延評価 |
| アプリ起動速度 |
OnStart が重くなりやすい |
起動をブロックしない |
| テスト・デバッグ | 変数の中身を確認する必要あり | 数式として直接検証可能 |
| 副作用 |
Set() は状態を変更する |
数式は純粋関数として振る舞う |
色変換関数のような入力が決まれば出力が一意に決まる純粋な計算は、Named Formulaが最も適した置き場所です。
5. 設計上のポイントまとめ
なぜフローでJSONを解析するのか
Power AppsはParseJSON()を持ちますが、深くネストした構造を再帰的にトラバースする手段がありません。ループと変数が使えるPower Automateで事前に展開し、フラットな配列に変換してからPower Appsに渡すのが現実的な設計です。
operands[2]を辿る反復処理
再帰構造を反復(Do until)で処理するには「現在位置ポインタ」パターンが有効です。PillObject 変数がまさにそれで、毎ループ operands[2] へ更新することでツリーを根から葉へ辿ります。
失敗を利用した末端検出
operands[2] が文字列になる末端では型エラーが発生します。これを runAfter: Failed で捕捉することで、明示的なnullチェック式を書かずに末端(デフォルト値)を検出しています。Power Automateならではのエラーフロー活用例です。
動的取得のメリット
列書式を変更しても(選択肢の追加・削除・色変更)、Power Appsのコードを一切変更する必要がありません。フローが毎回最新のJSONを解析するため、メンテナンスコストをSharePoint側の変更に集約できます。
6. まとめ
| レイヤー | 役割 |
|---|---|
| SharePoint Column Formatting | 選択肢ごとのCSSクラスを三項演算子ネストで定義 |
| Power Automate | REST APIでJSONを取得し、Do untilでフラットな配列に変換 |
| Power Apps(ParseJSON/Type) | フロー応答を型付きコレクションに変換して扱いやすくする |
| Power Apps(App.Formulas) | CSSクラス名文字列をRGBAカラー値にマッピング(Named Formula) |
SharePointの列書式JSONは「人間が読むもの」ではなくSharePointが解釈するDSLです。その内部構造を理解することで、Power Platform全体でスタイルを一元管理できるようになります。












