C# - JSON - JavaScriptSerializer 利用方法

## はじめに

WEB API クライアントアプリ作成時などで JSON を扱います。
C# での JSON シリアライザとしては、下記などがあります。

- [System.Text.Json](https://learn.microsoft.com/ja-jp/dotnet/api/system.text.json)
  - .NET Core 3.0以降（.NET5 以降も含む）で提供され、2021年からは System.Text.Json が推奨となっています。
  - .NET Framework（4.6.1以降）の場合は [NuGet Gallery | System.Text.Json](https://www.nuget.org/packages/System.Text.Json)  から取得することで利用可能です。
- [Json.NET](https://www.newtonsoft.com/json)
  - サードパーティ製ライブラリで [NuGet Gallery | Newtonsoft.Json](https://www.nuget.org/packages/Newtonsoft.Json) から取得できます。
  - 多機能で使い勝手が良いので、長い間、デファクトスタンダードとして利用されてきましたが、Microsoft が System.Text.Json を提供し、強烈にプッシュしているので、今後 System.Text.Json への書き換えが進むのでしょうか、、、
- [System.Web.Script.Serialization.JavaScriptSerializer](https://learn.microsoft.com/ja-jp/dotnet/api/system.web.script.serialization.javascriptserializer)
  - .NET Framework 3.5 で、ASP.NET - AJAX 対応アプリでの JSON シリアライザとして提供されました。
  - 上記目的で提供されていますが、.NET Framework デスクトップアプリでも利用可能です。
  - .NET Core 3.0以降（.NET5 以降も含む）では、利用不可です。
- [System.Runtime.Serialization.Json.DataContractJsonSerializer](https://learn.microsoft.com/ja-jp/dotnet/api/system.runtime.serialization.json.datacontractjsonserializer) 
  - .NET Framework 当初から提供されています。
- [Dynamic.Json](https://github.com/Groxan/Dynamic.Json)
  - C# 4.0 で導入された Dynamic 型を用いたライブラリで [NuGet Gallery | Dynamic.Json](https://www.nuget.org/packages/Dynamic.Json/) から取得できます。

.NET Framework 3.5 で動作するアプリを作成していたこともあり、サイドバイサイドにライブラリ配置が不要で、C# 標準 XMLシリアライザと同等の使い勝手で利用できる、 JavaScriptSerializer のお世話になることが多かったです。
今回は、JavaScriptSerializer 利用方法について記載しておこうと思います。


:::note
現時点での推奨は System.Text.Json ですから、基本的には System.Text.Json 利用が望ましいです。
[.NET Framework と .NET](https://qiita.com/chai0917/items/d939fa7aacff5f27e0ed) 記載内容を認識した上で、.NET Framework を利用し続ける場合、JavaScriptSerializer は、ひとつの選択肢となります。
:::



### テスト環境

ここに記載した情報/ソースコードは、Visual Studio Community 2022 を利用した下記プロジェクトで生成したモジュールを Windows 11 24H2 で動作確認しています。
- Windows Forms - .NET Framework 4.8
- WPF - .NET Framework 4.8

::: note
Visual Studio 2022 - .NET Framework 4.8 は、C# 7.3 が既定です。
このため、サンプルコードは、C# 7.3 機能範囲で記述しています。
:::

## JavaScriptSerializer

https://learn.microsoft.com/ja-jp/dotnet/api/system.web.script.serialization.javascriptserializer

### 参照設定
JavaScriptSerializer クラスを利用する場合、System.Web.Extensions.dll の参照追加が必要です。

```CSharp
using System.Web.Script.Serialization;
```

### Serialize
[Serialize](https://learn.microsoft.com/ja-jp/dotnet/api/system.web.script.serialization.javascriptserializer.serialize) で、オブジェクトを JSON 文字列に 変換します。

```CSharp
var obj = new Person();
obj.PersonID = 1234;
obj.Name = "hoge";
obj.Registered = true;

// シリアライズ
var serializer = new JavaScriptSerializer();
var json = serializer.Serialize(obj);
```

```CSharp
public class Person
{
  public int PersonID { get; set; }
  public string Name { get; set; }
  public bool Registered { get; set; }
}
```

### Deserialize
[Deserialize](https://learn.microsoft.com/ja-jp/dotnet/api/system.web.script.serialization.javascriptserializer.deserialize) で、指定した JSON 文字列を指定した型 T のオブジェクトに変換します。

```CSharp
var sb = new StringBuilder();
sb.Append("{\"PersonID\":1234,");
sb.Append("\"Name\":\"Hoge\",");
sb.Append("\"Registered\":true}");

// デシリアライズ
var serializer = new JavaScriptSerializer();
var obj = serializer.Deserialize<Person>(sb.ToString());
```

```CSharp
public class Person
{
  public int PersonID { get; set; }
  public string Name { get; set; }
  public bool Registered { get; set; }
}
```

### MaxJsonLength
JavaScriptSerializer クラスで受け入れ可能な JSON 文字列の最大長は [MaxJsonLength](https://learn.microsoft.com/ja-jp/dotnet/api/system.web.script.serialization.javascriptserializer.maxjsonlength) プロパティで参照/設定可能で、既定値は 2,097,152 文字となっています。  

シリアライズで出力が MaxJsonLength を超えた場合は InvalidOperationException の例外が発生します。
デシリアライズで入力が MaxJsonLength を超えている場合は ArgumentException の例外が発生します。

デシリアライズについては、事前にJSONサイズを確認すれば、MaxJsonLength についての例外は抑止できます。

シリアライズについては、前述例外時に、必要なサイズを変数なりで受け取れると、自然にリトライ処理につなぐことができたりするんですけど、、、
まぁ、対象オブジェクトの概算サイズを事前に確認とか、JSON の受け渡し相手 - WEB API 側などでも送受信サイズの最大値などを決めるので、それにあわせて MaxJsonLength を設定とかになります。

このため、下記のようにラップして利用することもありました。


```CSharp
// オブジェクトをJSON文字列に変換する
private string JsonSerialize(object obj, int maxJsonLength = 0)
{
    var serializer = new JavaScriptSerializer();
    if (maxJsonLength > serializer.MaxJsonLength)
    {
        serializer.MaxJsonLength = maxJsonLength;
    }
    return serializer.Serialize(obj);
}
// JSON文字列をオブジェクトに変換する
private T JsonDeserialize<T>(string json, int maxJsonLength = 0)
{
    var serializer = new JavaScriptSerializer();
    // JSONサイズが確認できるので確認
    maxJsonLength = maxJsonLength > json.Length ?
        maxJsonLength : json.Length;
    if (maxJsonLength > serializer.MaxJsonLength)
    {
        serializer.MaxJsonLength = maxJsonLength;
    }
    return serializer.Deserialize<T>(json);
}
```

:::note
上記メソッドの第二引数はオプション引数として初期値を設定しています。
オプション引数とすると、呼び出し時に対象引数を省略でき、省略時にここで指定した初期値が利用されます。

上記メソッドを例にすると下記記述は等価となります。
JsonSerialize(obj);
JsonSerialize(obj, 0);
:::