業務アプリの開発を想定した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ファイル上の”//”, “/* … */”のコメントはエラーになってしまいます。
外部システムとのデータ交換では、処理効率が優先されるため、冗長なデータとなるコメントは使用されません。設定ファイルの定義や動作確認・テストのような場面では保守性や開発効率を向上できるため、コメントの使用を許容します。
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”で出力する場合は、次のようにコンバータを定義します。
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にコンバータを追加します。var serializerOptions = new JsonSerializerOptions(); serializerOptions.Converters.Add(new BoolSampleConverter()); JsonSerializer.Serialize(writer, element, serializerOptions); - 特定プロパティの値を変換したい場合
プロパティに[JsonConverter]を指定します。class BoolExampleElement { ... [JsonConverter(typeof(BoolYesNoConverter))] public bool BoolYesNoValue { get; set; } ... } - 特定クラスを変換したい場合(M:N変換、複雑な構造の変換時)
クラス定義に[JsonConverter]を指定します。public 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”等の変換に使用する具体的な値を派生クラスで定義しています。
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等の整数に変換するコンバータの例を次に示します。
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″等の業務独自の日付書式を使うことが多いため、このような日付書式を扱うコンバータの例を次に示します。
前述のコンバータ同様、値の読み取り・書き込み等の共通の処理を基底クラスとして実装し、日付書式を派生クラスで定義しています。
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が出力さます。
enum Status
{
Start = 1, End = 2
}列挙型の値名の出力
列挙型の整数ではなく、”Start”, “End”等の列挙型の値名で出力する場合は、既定のJsonStringEnumConverterを使用できます。
JsonStringEnumConverterのコンストラクタの引数指定でキャメルケース形式で値を出力することもできますが、これはJsonSerializerOptions.Convertersに追加する方法で指定する必要があります。(または独自のコンバータを作成するか。)
class EnumExampleElement
{
...
[JsonConverter(typeof(JsonStringEnumConverter))]
public Status StatusStringValue { get; set; }
...
}列挙型をカスタム値で出力
列挙型を独自の値に変換するコンバータの例を次に示します。
この例では、前述のStatus列挙型のStart/Endを、”begin”/”finish”に変換します。
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型のプロパティを指定すれば、ほぼ同様のことができると思います。)
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);
}
}