System.Text.Json のデシリアライズ失敗で出る例外の切り分け。API 連携やキューメッセージで頻出。
先に結論
- まず生の応答本文と、当てた型を並べて見る
- 原因の型: 構文壊れ / 型不一致 / 必須プロパティ / 名前の付け方 / 日付形式
- 例外メッセージの Path(例: items の 0 番目)が手がかり
- Newtonsoft と System.Text.Json で属性・既定挙動が違う
よくある原因
1. 本文自体が壊れている
- 末尾カンマ、引用符抜け、HTML のエラーページが返ってきた
- 文字コードや BOM
- 失敗時は本文の先頭だけログ(秘密情報に注意)
2. 型が合わない
数値が文字列で来ている、配列なのにオブジェクトを期待している、など。DTO のプロパティ型を疑う。
3. プロパティ名の不一致
- camelCase / PascalCase / snake_case の食い違い
- JsonPropertyName 属性で明示するか、PropertyNameCaseInsensitive を検討
4. null と必須
- 欠落プロパティ、null 非許容、required メンバ
- どの欠落を許容するかはオプションと型設計次第
切り分け手順
- 例外メッセージと Path を読む
- 実際の本文(マスク済み)を保存
- 同じ本文を小さいテストで Deserialize して再現
- 怪しいプロパティだけ string 受けにして切り分け
- フレームワークのモデルバインド失敗(400)と、自前 Deserialize 失敗を分ける
実装の定石
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
};
try
{
var order = JsonSerializer.Deserialize<OrderDto>(payload, options)
?? throw new InvalidOperationException("payload was null");
}
catch (JsonException ex)
{
_logger.LogWarning(ex, "parse failed at {Path}", ex.Path);
throw;
}
ざっくりまとめ
- 本文と型と Path を見る
- 構文・型・名前・null の順で疑う
- 失敗ログに Path を残す(本文全体の出しすぎに注意)