SharePoint リストの Person 列(複数選択)を REST API で取得するとき、Approvers に「メールアドレス未設定のユーザー」が混ざると Approvers/EMail の投影でエラーになることがあります。本記事では現象の整理、なぜ起きるのか、そして実務で確実に回避する設計・実装パターンを具体例つきでまとめます。
起きている現象(再現条件とエラーメッセージ)
前提として、SharePoint リストに「ユーザー/グループ(Person)」列があり、列名は Approvers(複数選択) とします。アイテムの Approvers に、何らかの理由で メールアドレス(Work email / mail 属性)が空 のユーザーが含まれているケースです。
| 項目 | 内容 |
|---|---|
| 列の種類 | ユーザー/グループ(Person) |
| 列の設定 | 複数選択(複数ユーザーを許可) |
| 混入しているデータ | メールアドレス未設定(空 / null 相当)のユーザー |
| REST API でやりたいこと | Approvers/EMail を 投影(project) して 1 回で取得したい |
| 発生するエラー | Cannot get value for projected field Approvers_x005f_EMail. |
| Graph API の挙動 | email が空(空文字 / null)で返り、取得自体は成功しやすい |
典型的に問題が出る REST API の例は次のようなクエリです(& は HTML の都合で & 表記にしています)。
GET https://{tenant}.sharepoint.com/sites/{site}/_api/web/lists/getbytitle('ListName')/items
?$select=*,Approvers/EMail
&$expand=Approvers
すると、メールアドレスが空のユーザーを 1 人でも含むアイテムに当たった瞬間に、レスポンス全体が失敗しやすくなります。ここが厄介なポイントで、アプリ側で「空なら空として扱う」以前に、API 呼び出し自体が落ちるためです。
「投影(project)」が地雷になりやすい理由
この問題の核心は、SharePoint REST API が Approvers/EMail のような形で取得するとき、内部的に「投影フィールド(projected field)」として計算した値を返そうとする点にあります。エラーメッセージの Approvers_x005f_EMail は、内部名として Approvers_EMail をエンコードしたような形(_x005f_ はアンダースコア相当)で、“Approvers から EMail を投影した結果” を意味します。
Person 列は、アイテム側に「ユーザーそのもの」が格納されているというより、参照(lookup)としてユーザー情報へリンクしています。ここで参照先ユーザーのプロパティが欠けている(今回なら EMail が空)と、投影の計算が成立せず、結果として REST API は 400/500 系で落ちることがあります。これはアプリのバグというより、REST API(OData 由来の投影の仕組み)の制約に引っかかっている整理が現実的です。
一方 Microsoft Graph は、設計として「欠損しているプロパティは null(または空)で返す」方向の挙動が多く、メールが空でもアイテム取得が成立するケースが目立ちます。つまり、同じ “email が空” でも、REST は投影計算の段階で失敗しやすく、Graph は欠損として返してくれるという差が表に出ます。
そして重要なのはここです。SharePoint REST API 側で Graph のように「欠損を空で返す」モードへ切り替えるオプションは、基本的に期待しないほうがよいという点です。挙動を変えるスイッチがあるなら一番楽ですが、実務上は「落ちない取得方法に設計を寄せる」ほうが確実です。
回避策A:Approvers/EMail を“直接投影しない”取得パターンに変える
最も堅牢で、REST API を使い続けるなら現実的なのがこの回避策です。ポイントは 2 つあります。
- リストアイテム取得では、メールのような欠損しうるプロパティを投影しない
- 必要なら、ユーザー ID など 落ちにくいキーで受けてから別ルートでメールを解決する
パターンA-1:まずは “ユーザーIDだけ” を安全に取る(おすすめ)
Person 列は、REST では {列内部名}Id として参照先の ID を取り出せます。複数選択なら results 配列で返ります。これを使うと、ユーザーオブジェクトのプロパティ投影を避けたまま、「誰が入っているか」を取得できます。
GET https://{tenant}.sharepoint.com/sites/{site}/_api/web/lists/getbytitle('ListName')/items
?$select=Id,Title,ApproversId
レスポンス例(イメージ):
{
"d": {
"results": [
{
"Id": 100,
"Title": "申請A",
"ApproversId": { "results": [ 23, 57 ] }
}
]
}
}
この段階では “メール” に触れていないため、メール未設定ユーザーが混ざっていても落ちにくいのが強みです。
パターンA-2:ユーザーIDからメールを解決する(単発・バッチ・キャッシュ)
次に、取得したユーザー ID を使ってユーザー情報を引きます。代表的には以下のようなエンドポイントを使います(環境・権限で選択肢は変わります)。
| 方法 | 例 | 特徴 |
|---|---|---|
| 単発で取得 | /_api/web/getuserbyid(23) | 実装が単純だが、ユーザー数が多いと N+1 になりやすい |
| SiteUsers を filter | /_api/web/siteusers?$filter=(Id eq 23) or (Id eq 57) | 条件をまとめられるが、ID が多いと URL が長くなる |
| $batch を使う | /_api/$batch | HTTP 往復を抑えられ、実務では扱いやすい |
| キャッシュする | id→メール辞書を保持 | 同じユーザーが何度も出る業務では効果が大きい |
単発取得の例(必要最小限だけ select する):
GET https://{tenant}.sharepoint.com/sites/{site}/_api/web/getuserbyid(23)
?$select=Id,Title,EMail,LoginName
ここで EMail が空でも、(少なくとも投影フィールドではなく)ユーザー単体のプロパティとして返るため、“空として処理する” 余地が生まれます。アプリ側では「空なら空として扱う」「UPN/ログイン名にフォールバックする」などの分岐が可能になります。
パターンA-3:アプリ側で“欠損前提”のモデルにして落とさない
メールが必須でない組織・テナントは実際にあります。外部ユーザー、特定のサービスアカウント、古いディレクトリ同期の名残など、理由は様々です。そのため Person 列を扱うアプリでは、次のような発想が安定します。
- 承認者の主キーは「メール」ではなく ユーザー ID / LoginName とする
- メールは通知や表示のための “補助情報” として扱い、空でも業務が止まらないようにする
- 通知先の決定は「mail が空なら UPN を使う」「mail が空なら Teams 通知に切り替える」など 運用に寄せたフォールバックを用意する
TypeScript の簡易イメージ:
// user: SharePoint の SiteUser 相当を想定
function resolveNotificationAddress(user: any): string | null {
const mail = (user?.EMail ?? "").trim();
if (mail) return mail;
// 代替案:ログイン名や UPN が取れるなら、運用に合わせて使う
const login = (user?.LoginName ?? "").trim();
if (login) return login;
return null; // 最終的に通知不可として扱う(別ルートで対応)
}
ここまでを整理すると、「REST API の投影を避ける → ID を基点にユーザー情報を解決 → 欠損は欠損として扱う」という設計が、メール未設定ユーザーが混ざる現実に強い実装になります。
回避策B:Microsoft Graph API を継続利用する
要件として「メール未設定は空で返してほしい」「取得自体は必ず成功してほしい」という優先度が高いなら、Graph API を採用し続ける判断は合理的です。実際、Graph は欠損プロパティを null/空として返すことが多く、今回のような “投影フィールドの計算失敗” に巻き込まれにくい傾向があります。
ただし、Graph を採る場合も次の点は押さえておくと安全です。
| 観点 | 注意点 | 実務的な対策 |
|---|---|---|
| メールは必ずあるとは限らない | Graph でも mail は空のことがある | 空なら UPN、ID、displayName などへフォールバック |
| 権限設計が別物 | Graph の権限・同意・アプリ登録が必要 | 最小権限で設計し、運用手順を整備する |
| 戻り値の形が REST と違う | Person 値の型・表現が異なる | フロント/バッチの変換層を用意して吸収 |
| “落ちない” を過信しない | 通信・権限・スロットリングは起こりうる | リトライ、キャッシュ、部分成功の扱いを定義 |
Graph を使う場合でも、メールを主キーにしない、欠損前提で扱うというデータ設計は結果的に強くなります。Graph が “空で返してくれる” のは助かりますが、空である以上はアプリ側で扱いを決める必要があるためです。
補足:根本対応として「メール属性が空のユーザー」を減らす
技術的回避は重要ですが、運用上許されるなら根本対応も検討価値があります。つまり、メールが空のユーザーを減らすことです。
現場で実際に効くアプローチを、影響が小さい順に並べます。
| 対応 | 狙い | 現場での現実性 |
|---|---|---|
| メール未設定ユーザーの棚卸し | どの種類のユーザーが空なのか把握する | 高(まずやるべき) |
| 運用ルール化(承認者に使えるアカウントの定義) | 承認者に入れてよいユーザーを制限する | 中(部門間調整が必要) |
| ディレクトリ側の属性整備(Entra ID / AD) | mail 属性や必要プロパティを統一する | 中〜低(管理者権限・影響範囲が大きい) |
| ユーザープロファイルの整備 | SharePoint 側参照の欠損を減らす | 低(環境依存・運用負荷が出やすい) |
ただし、メール未設定が “例外” ではなく “仕様” に近い組織もあります。例えば、共有端末用アカウント、外部連携用の特殊アカウント、移行時の残骸などです。その場合は「属性を埋める」よりも、アプリのデータモデルを欠損耐性にするほうがトータルコストが下がることが多いです。
実務で困らないための設計パターン(おすすめ構成)
ここまでを踏まえ、Person 列(複数選択)を API 経由で扱うときの “壊れにくい” 構成例を紹介します。REST/Graph どちらでも通用する考え方です。
おすすめのデータフロー
| ステップ | 何を取得するか | 落ちにくさ | ポイント |
|---|---|---|---|
| 1 | アイテム本体 + ApproversId | 高 | まずキー(ID)を取る。メールはここで触らない |
| 2 | ユーザー情報(Id→EMail/表示名など) | 中〜高 | 空を許容。N+1 を避けるためバッチ/キャッシュを検討 |
| 3 | 通知/表示の組み立て | 高 | mail が空なら代替ルート(UPN/別チャネル/手動)を用意 |
「1回で全部取る」より「壊れない2段階」を選ぶ価値
一見すると、$select=Approvers/EMail&$expand=Approvers の “1回で全部” は美しいです。しかし、運用データは必ず揺れます。承認者に想定外のアカウントが入ることもあります。そのときに API が落ちる設計だと、障害対応がアプリ側に集中します。
2段階取得は面倒に見えますが、次のメリットがあります。
- 欠損データが 1 件混ざっても、アイテム取得が止まらない
- メール以外の属性(表示名、部署、UPN など)も後段で柔軟に扱える
- ユーザー解決部分をキャッシュすれば、むしろパフォーマンスが安定する
よくある落とし穴と対処
「Approvers を expand してるのに、結局メールが取れない」
expand の仕方や select の仕方によっては、必要なプロパティが返らないことがあります。メールが不安定な環境では、最初から “メールを返してもらう” ことに固執せず、ApproversId を基点にユーザー解決を別処理に切り出すのが確実です。
「ユーザー解決で API 呼び出し回数が増える」
これは事実なので、以下のいずれかを組み合わせるのが現実的です。
- アイテム群からユーザー ID をユニーク化してまとめて引く
- SharePoint の
/_api/$batchを使い、往復回数を削減する - id→ユーザー情報のキャッシュ(メモリ、Redis、IndexedDB など)を導入する
「メールが空だと困る(通知できない)」
技術だけで解決しない要件です。運用設計として、最低でも次のどれかを決めておくと事故が減ります。
- 承認者に入れてよいユーザー種別を制限する
- mail が空のときは UPN を通知先にする(可能な場合)
- mail が空のときは Teams 通知に切り替える/管理者へエスカレーションする
Microsoft への要望として動くなら
「REST API 側も Graph のように欠損を空で返してほしい」という要望は自然です。ただ、現場の開発者が REST API の挙動に直接パッチを当てることはできません。もし組織として改善要望を出したいなら、Microsoft の開発者向けコミュニティ(Tech Community など)で同様の課題を共有し、再現条件・要望・影響範囲を整理して伝えるのが現実的なルートになります。
まとめ
SharePoint REST API で Person 列(複数選択)の Approvers/EMail を投影すると、参照先ユーザーのメールが未設定なだけで Cannot get value for projected field Approvers_x005f_EMail. のように取得自体が失敗することがあります。これをアプリ側で “空として扱う” 前に落ちるのがポイントです。
実務での最適解は、「投影でメールを取る」発想から離れ、ApproversId など落ちにくいキーを先に取り、ユーザー解決は別処理で行い、欠損は欠損として扱うことです。Graph API が要件に合うなら採用を継続するのも有効ですが、どちらにせよ “メールは欠損しうる” 前提でデータモデルを作ると運用が安定します。

コメント