業務アプリの開発を想定した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); } } |