業務アプリの開発を想定したJsonSerializerの使い方とサンプルです。
概要
- .NET Core 3.1の標準パッケージSystem.Text.Jsonに含まれるJsonSerializerを使って、クラス・プロパティの値をJSONに変換(シリアライズ)、JSONの値をクラス・プロパティに変換(デシリアライズ)する方法を説明します。
- Visual Studio 2019 + .NET Core 3.1(C#)の環境を前提とします。
- サンプルの完全なソースコードはこちらで公開しています。
- クラス・プロパティやJSONの値に日本語(UTF8)を含める前提のサンプルや説明になっています。
- 前提となる基本的な使い方は下記を参考のこと。
業務想定のサンプル
- 日本語(UTF8)の値の読み取り・書き込みUTF8形式のファイルをJSONにシリアライズする場合は良いのですが、JSONに出力するとUnicodeエスケープされてしまいます。(例えば、「あ」は「\u3042」のように出力される。)
 JsonSerializerOptions.Encoderでエスケープしない文字(範囲)を指定することで、日本語のUnicodeエスケープを抑制します。
- 既定の日付書式の変更DateTime型の既定の日付書式は、”2020/11/22T03:23:45″, “2020/11/22T12:23:45+09:00″等のISO形式です。この書式は業務によって変わってくるので、カスタマイズできるようにします。詳細は後述します。
- JSONフィールド名にキャメルケースを使用既定では、JSONに出力されるフィールド名はパスカルケースになります。例えば、MyNameプロパティは”MyName”というフィールド名で出力されます。経験的にキャメルケースのJSONを扱うことが多いので、ここではキャメルケース(“myName”)で出力されるように変更します。
- コメントのスキップ既定ではJSONファイル上の”//”, “”のコメントはエラーになってしまいます。
 外部システムとのデータ交換では、処理効率が優先されるため、冗長なデータとなるコメントは使用されません。設定ファイルの定義や動作確認・テストのような場面では保守性や開発効率を向上できるため、コメントの使用を許容します。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | public class Example3 {     public static async Task Run()     {         var json = await LoadJson("input.json");         await WriteJson("output.json", json);     }     public static async Task<BasicExampleElement> LoadJson(string filename)     {         // デシリアライズ時のオプション         var serializerOptions = new JsonSerializerOptions()         {             // キャメルケースとプロパティ名の相違に対応             // ・例: JsonのmyNameフィールドをMyNameプロパティに対応付け             // ・大小文字を区別する場合は[JsonPropertyName]を使用のこと             PropertyNameCaseInsensitive = true,             // コメントをスキップ             ReadCommentHandling = JsonCommentHandling.Skip         };         // DateTimeの既定の日付書式を変更         // (既定のISO形式ではなく独自の日付書式に変更)         serializerOptions.Converters.Add(new DateTimeLocalConverter());         // JSONの読み取り         // ※日本語を含むUTF8(BOMなし)のファイルを前提         using var stream = new FileStream(filename, FileMode.Open);         return await JsonSerializer.DeserializeAsync<BasicExampleElement>(stream, serializerOptions);     }     public static async Task WriteJson(string filename, BasicExampleElement element)     {         // シリアライズ時のオプション         var serializerOptions = new JsonSerializerOptions()         {             // 日本語文字のユニコードエスケープを防止             // (例えば "あ" が "\u3042" のようにエスケープされないようにする。)             Encoder = JavaScriptEncoder.Create(UnicodeRanges.All),             // 人が参照しやすいようインデントを付与(不要であれば、このオプションは除外)             WriteIndented = true,             // キャメルケースでJsonフィールドを使用(既定はパスカルケース)             // (例: MyNameプロパティは、myNameというJsonフィールドとして出力)             PropertyNamingPolicy = JsonNamingPolicy.CamelCase         };         // DateTime既定の日付書式を変更         serializerOptions.Converters.Add(new DateTimeLocalConverter());         // JSONの出力         using var stream = new FileStream(filename, FileMode.OpenOrCreate | FileMode.CreateNew);         await JsonSerializer.SerializeAsync(stream, element, serializerOptions);     } } | 
- シリアライザで指定可能なオプションは次をご覧ください。
変換のカスタマイズ
- クラス・プロパティとJSONフィールドの変換はコンバータによって実現されています。
- 標準で多様なコンバータが提供されています。
- 標準のコンバータでは実現できない変換については、独自のコンバータを定義することもできます。
 以降では、典型的なコンバータのサンプルを説明します。
- コンバータを使う方法以外にも、ファクトリーパターン(JsonConverterFactory)を使用する方法もありますが、こちらは別途説明予定です。
コンバータの使用方法
変換仕様を実装するためのコンバータの定義方法、定義したコンバータを特定の型やプロパティに指定する方法を説明します。
コンバータの定義方法
JsonConverterから派生したクラスを定義することでコンバータを作成できます。
例えば、bool型のtrue/falseをJSONに”yes”/”no”で出力する場合は、次のようにコンバータを定義します。
| 1 2 3 4 5 6 7 | public class BoolSampleConverter : JsonConverter<bool> {     public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)         => reader.GetString().Equals("yes"); // "yes"ならtrue、それ以外はfalse     public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options)         => writer.WriteStringValue(value ? "yes" : "no"); } | 
4行目のようにReadメソッドで「JSONから取得した文字列をどのように型に変換するか」を定義します。
逆に、6行目のようにWriteメソッドで「型の値をどのようにJSONに出力するか」を定義します。
コンバータの指定方法
定義したコンバータの指定方法は次の3つの方法があります。
- 特定の型に対する変換を変更したい場合(既定のコンバータの変更)
 JsonSerializerOptions.Convertersにコンバータを追加します。123var serializerOptions = new JsonSerializerOptions();serializerOptions.Converters.Add(new BoolSampleConverter());JsonSerializer.Serialize(writer, element, serializerOptions);
- 特定プロパティの値を変換したい場合
 プロパティに[JsonConverter]を指定します。1234567class BoolExampleElement{...[JsonConverter(typeof(BoolYesNoConverter))]public bool BoolYesNoValue { get; set; }...}
- 特定クラスを変換したい場合(M:N変換、複雑な構造の変換時)
 クラス定義に[JsonConverter]を指定します。12345678910111213public class ClassExampleElement{public string SiteName { get; set; }public UriElement Uri { get; set; }}[JsonConverter(typeof(UrlElementConverter))]public class UriElement{public string Scheme { get; set; }public string Hostname { get; set; }public int PortNumber { get; set; }}
bool型コンバータの例
bool型のtrue/falseを”yes”/”no”、”on”/”off”等の文字列に変換するコンバータの例を次に示します。
値の読み取り・書き込み等の共通の処理を基底クラスとして実装し、”yes”/”no”等の変換に使用する具体的な値を派生クラスで定義しています。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | public class BoolStringConverterBase : JsonConverter<bool> {     public string TrueValue { get; }     public string FalseValue { get; }     public bool IgnoreCase { get; }     public BoolStringConverterBase(string trueValue, string falseValue, bool ignoreCase = true)     {         TrueValue = trueValue;         FalseValue = falseValue;         IgnoreCase = ignoreCase;     }     public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)         => reader.GetString().Equals(TrueValue, IgnoreCase ?             StringComparison.CurrentCultureIgnoreCase :             StringComparison.CurrentCulture);     public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options)         => writer.WriteStringValue(value ? TrueValue : FalseValue); } public class BoolYesNoConverter : BoolStringConverterBase {     public BoolYesNoConverter() : base("yes", "no") { } } public class BoolOnOffConverter : BoolStringConverterBase {     public BoolOnOffConverter() : base("on", "off") { } } | 
bool型のtrue/falseを1/0等の整数に変換するコンバータの例を次に示します。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | public class BoolIntConverterBase : JsonConverter<bool> {     public int TrueValue { get; }     public int FalseValue { get; }     public BoolIntConverterBase(int trueValue, int falseValue)     {         TrueValue = trueValue;         FalseValue = falseValue;     }     public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)         => reader.GetInt32().Equals(TrueValue);     public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options)         => writer.WriteNumberValue(value ? TrueValue : FalseValue); } public class BoolOneZeroConverter : BoolIntConverterBase {     public BoolOneZeroConverter() : base(1, 0) { } } | 
DateTime型コンバータの例
DateTime型は既定で、”2020/11/22T03:23:45″, “2020/11/22T12:23:45+09:00″等のISO形式の日付書式になります。
私の経験的にISO形式は使用することは少なく、”2020/11/22 12:23:45″, “20201122”, “2020-06″等の業務独自の日付書式を使うことが多いため、このような日付書式を扱うコンバータの例を次に示します。
前述のコンバータ同様、値の読み取り・書き込み等の共通の処理を基底クラスとして実装し、日付書式を派生クラスで定義しています。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | public class DateTimeConverterBase : JsonConverter<DateTime> {     public string DateTimeFormat { get; }     public DateTimeConverterBase(string dateTimeFormat)     {         DateTimeFormat = dateTimeFormat;     }     public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)         => DateTime.ParseExact(reader.GetString(), DateTimeFormat, CultureInfo.CurrentCulture);     public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)         => writer.WriteStringValue(value.ToString(DateTimeFormat)); } public class DateTimeLocalConverter : DateTimeConverterBase {     public DateTimeLocalConverter() : base("yyyy/MM/dd HH:mm:ss") { } } public class DateTimeYmdConverter : DateTimeConverterBase {     public DateTimeYmdConverter() : base("yyyyMMdd") { } } public class DateTimeYmHyphenConverter : DateTimeConverterBase {     public DateTimeYmHyphenConverter() : base("yyyy-MM") { } } | 
列挙型コンバータの例
列挙型の既定の出力)
既定では、列挙型は整数に変換されます。
例えば、次のStatus列挙型のプロパティの値がStartの場合、JSONファイルには1が出力さます。
| 1 2 3 4 | enum Status {     Start = 1, End = 2 } | 
列挙型の値名の出力
列挙型の整数ではなく、”Start”, “End”等の列挙型の値名で出力する場合は、既定のJsonStringEnumConverterを使用できます。
JsonStringEnumConverterのコンストラクタの引数指定でキャメルケース形式で値を出力することもできますが、これはJsonSerializerOptions.Convertersに追加する方法で指定する必要があります。(または独自のコンバータを作成するか。)
| 1 2 3 4 5 6 7 | class EnumExampleElement { ...     [JsonConverter(typeof(JsonStringEnumConverter))]     public Status StatusStringValue { get; set; } ... } | 
列挙型をカスタム値で出力
列挙型を独自の値に変換するコンバータの例を次に示します。
この例では、前述のStatus列挙型のStart/Endを、”begin”/”finish”に変換します。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | public class StatusEnumStringConverter : JsonConverter<Status> {     public override Status Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)     {         switch (reader.GetString())         {             case "begin": return Status.Start;             case "finish": return Status.End;             default: return default;         }     }     public override void Write(Utf8JsonWriter writer, Status value, JsonSerializerOptions options)     {         string result;         switch (value)         {             case Status.Start: result = "begin"; break;             case Status.End: result = "finish"; break;             default: result = string.Empty; break;         }         writer.WriteStringValue(result);     } } | 
クラスコンバータの例
URLのスキーム/ホスト名/ポート番号をプロパティとして持つクラスを、JSONのURIに変換するコンバータの例を次に示します。
(あくまでもサンプルであり、単純にURI型のプロパティを指定すれば、ほぼ同様のことができると思います。)
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | public class UrlElementConverter : JsonConverter<UriElement> {     public override UriElement Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)     {         var uri = new Uri(reader.GetString());         return new UriElement()         {             Scheme = uri.Scheme,             Hostname = uri.Host,             PortNumber = uri.Port         };     }     public override void Write(Utf8JsonWriter writer, UriElement value, JsonSerializerOptions options)     {         var uri = new Uri($"{value.Scheme}://{value.Hostname}:{value.PortNumber}");         writer.WriteStringValue(uri.AbsoluteUri);     } } | 
