C# API処理時のデシリアライズについて

## ■概要

C#でのHTTP通信を用いたAPI処理時のデシリアライズについてまとめました。
API処理の詳細は以前記載した以下。

https://qiita.com/Unnmo/items/e2d7fc79bbf1aaddfbc7

上記では、`Newtonsoft.Json`を用いていたが今回は`System.Net.Http.Json`を用いて記載。

## ■デシリアライズとは

JSON文字列やバイト型に変換されたデータを元の複合的なデータ構造やオブジェクトに
復元すること。
**逆シリアル化**とも呼ぶ。
→つまり、APIを呼び出して受け取ったJSON文字列を扱いやすい値に復元すること。

今回はHTTP通信を用いるため`System.Net.Http.Json`を使用。

https://learn.microsoft.com/ja-jp/dotnet/fundamentals/networking/http/httpclient

**※その他API処理について**
その他API処理については、`System.Text.Json`や`Newtonsoft.Json`があるが今回は割愛。
以下の記事が参考になる。

＞引用

https://qiita.com/SY81517/items/1cf6246dd99869f7b9c5

## ■デシリアライズ処理について
`System.Net.Http.Json`に含まれるデシリアライズ処理についてもいくつかの方法がある。


・**`GetFromJsonAsync`**
データ形式がJSONデータと分かっており一発でデイシリアライズしたい場合に使う。
HTTPリクエスト&レスポンスとデシリアライズを**一度に行う**ことが可能。

一方、内部でレスポンスを受け取った後レスポンスボディ（JSONデータ）全体を
一度メモリに保持してからデシリアライズを行うため、データサイズが大きい場合は
メモリの負担が増える。
 ```c#:GetFromJsonAsync
var todos = await httpClient.GetFromJsonAsync<Todo>("******");
```

・**`ReadFromJsonAsync`**
`GetAsync`でリクエスト&レスポンスを行い、`ReadFromJsonAsync`でデシリアライズを
行うため**任意のタイミング**でデシリアライズが可能。

レスポンスボディ（JSONデータ）をストリームとして読み込みつつ、同時にデシリアライズを行うためメモリ効率が良い。

```c#:ReadFromJsonAsync
using HttpResponseMessage response = await httpClient.GetAsync("******");
T? result = await response.Content.ReadFromJsonAsync<T>();
```


| 機能              | GetFromJsonAsync                      | ReadFromJsonAsync                 |
|-------------------|---------------------------------------|-----------------------------------|
| リクエストの実行   | HTTPリクエスト + デシリアライズを同時に処理 | デシリアライズのみ                  |
| 対応するHTTPメソッド | GET専用                              | 任意のメソッド（GET, POST, etc.）   |
| コードの簡潔さ     | 簡潔に書ける                         | 柔軟な処理が可能     

今回は、応答本文JSONの中身の確認等デバッグしたいため`ReadFromJsonAsync`を使用


## ■コード①：応答本文がオブジェクト型{ }
天気を取得するAPI処理
```c#:Weather.cs

//=====================================
//デシリアライズした値を格納するプロパティ
//=====================================
public class WeatherData
{
    public string Name { get; set; }
    public MainData Main { get; set; }
    public Weather[] Weather { get; set; }
    public Wind Wind { get; set; }
    public Clouds Clouds { get; set; }
}
public class MainData
{
    public double Temp { get; set; }
    public double Feels_Like { get; set; }
    public int Humidity { get; set; }
}
public class Weather
{
    public string Description { get; set; }
}
public class Wind
{
    public double Speed { get; set; }
}
public class Clouds
{
    public int All { get; set; }
}

//=====================================
//API処理
//=====================================
internal class Api
{
    public async Task GetNowWeather()
    {
        //エンドポイント(URL)作成
        string wetherDayUrl = $"https://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&appid={strApiKey}&lang={lang}&units=metric";
    
        try
        {
            using (HttpClient client = new HttpClient())
            {
                //HTTPリクエストの送信・レスポンス
                HttpResponseMessage response = await client.GetAsync(wetherDayUrl);

                //レスポンスが成功か確認
                response.EnsureSuccessStatusCode();
        
                //応答本文の読み取り
                string responseString = await response.Content.ReadAsStringAsync();
                //Console.WriteLine(responseString);
                
                //デシリアライズ
                var weatherData = await response.Content.ReadFromJsonAsync<WeatherData>();
        
                // 整形して表示
                Console.WriteLine($"都市: {weatherData.Name}");
                Console.WriteLine($"現在の気温: {weatherData.Main.Temp}°C");
                Console.WriteLine($"体感温度: {weatherData.Main.Temp}°C");
                Console.WriteLine($"天気: {weatherData.Weather[0].Description}");
                Console.WriteLine($"湿度: {weatherData.Main.Humidity}%");
                Console.WriteLine($"風速: {weatherData.Wind.Speed} m/s");
                Console.WriteLine($"雲量: {weatherData.Clouds.All}%");
            }
        }
        catch (Exception e)
        {
             Console.WriteLine($"エラー: {e.Message}");
        }
    }
}
```

```c#:応答本文
//============================
//応答本文(responseString)の中身
//============================
{
    "coord": {
        "lon": 139.7595,
        "lat": 35.6828
    },
    "weather": [
        {
            "id": 801,
            "main": "Clouds",
            "description": "薄い雲",
            "icon": "02n"
        }
    ],
    "base": "stations",
    "main": {
        "temp": 9.47,
        "feels_like": 5.34,
        "temp_min": 8.28,
        "temp_max": 10.53,
        "pressure": 1012,
        "humidity": 33,
        "sea_level": 1012,
        "grnd_level": 1010
    },
    "visibility": 10000,
    "wind": {
        "speed": 10.8,
        "deg": 350
    },
    "clouds": {
        "all": 20
    },
    "dt": 1734161818,
    "sys": {
        "type": 2,
        "id": 268395,
        "country": "JP",
        "sunrise": 1734126166,
        "sunset": 1734161315
    },
    "timezone": 32400,
    "id": 1861060,
    "name": "日本",
    "cod": 200
}
```

応答本文(JSON)の一番外側がオブジェクト型{ }の場合は、通常のデシリアライズで
問題なし。
→`.ReadFromJsonAsync<WeatherData>()`


## ■コード②：応答本文がリスト型[ ]
緯度・経度データを取得するAPI処理。
```c#:Geocording.cs
//=====================================
//デシリアライズした値を格納するプロパティ
//=====================================
public class GeocordingData
{
    public string Name { get; set; }
    public double Lat { get; set; }    //緯度
    public double Lon { get; set; }    //経度
}

//=====================================
//API処理
//=====================================
internal class Api
{
    public static async Task GetGeocording()
    {
        //エンドポイント(URL)作成
        string geocodingUrl = $"http://api.openweathermap.org/geo/1.0/direct?q={strCtiyName}&appid={strApiKey}";
        
        try
        {
            using (HttpClient client = new HttpClient())
            {
                //HTTPリクエストの送信・レスポンス
                HttpResponseMessage response = await client.GetAsync(geocodingUrl);

                //レスポンスが成功か確認
                response.EnsureSuccessStatusCode();
        
                //応答本文の読み取り
                string responseString  = await response.Content.ReadAsStringAsync();
                //Console.WriteLine(responseString );
        
                //デシリアライズ
                var geocodingDataList = await response.Content.ReadFromJsonAsync<List<GeocordingData>>();
                var geocodingData = geocodingDataList[0];
        
                // 整形して表示
                Console.WriteLine($"都市: {geocodingData.Name}");
                Console.WriteLine($"緯度: {geocodingData.Lat}");
                Console.WriteLine($"経度: {geocodingData.Lon}");
            }
        }
        catch (Exception e)
        {
             Console.WriteLine($"エラー: {e.Message}");
        }
    }
}
```


```c#:応答本文
//============================
//応答本文(responseString)の中身
//============================
[
    {
        "name": "Tokyo",
        "local_names": {
            "de": "Tokio",
            "ia": "Tokyo",
            "ja": "東京都",
        },
        "lat": 35.6828387,
        "lon": 139.7594549,
        "country": "JP"
    }
]
```

応答本文(JSON)の一番外側がリスト型[ ]の場合は、デシリアライズする際も
リスト型にする必要がある。
→`.ReadFromJsonAsync<List<GeocordingData>>()`

もし単一のオブジェクトとしてデシリアライズしようとするとエラーになる。
→`.ReadFromJsonAsync<GeocordingData>()`