• システム開発に関わる内容をざっくりと書いていく

C# JsonException / デシリアライズ失敗の原因と対策

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 メンバ
  • どの欠落を許容するかはオプションと型設計次第

切り分け手順

  1. 例外メッセージと Path を読む
  2. 実際の本文(マスク済み)を保存
  3. 同じ本文を小さいテストで Deserialize して再現
  4. 怪しいプロパティだけ string 受けにして切り分け
  5. フレームワークのモデルバインド失敗(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 を残す(本文全体の出しすぎに注意)