System.Text.Jsonのコンバーターメモ
データを POST するときは文字列、 GET するときはオブジェクトという C# 泣かせの構造な API に出会ってしまったため、 System.Text.Json の Converter を作った時のメモです。
公式のドキュメントはこちら。
やりたいこと
冒頭に記述したように、今回の対象は POST と GET で異なる型の C# 泣かせな JSON です。
実際の JSON はこのようになっています。
// POST { "key": "abcde" } // GET { "key": { "link": "https://xxxxxx/abcde", "value": "abcde } }
これを Converter を駆使してイイカンジに POCO にマッピングします。
// 対象の POCO public class LinkObject { public string? Link { get; set; } public string? Value { get; set; } }
Converter を実装する
冒頭にリンクを張ったドキュメントによると System.Text.Json の Converter 仕様は色々あるようです。今回は複雑なものでは無いためシンプルに JsonConverter<T>
を実装します。
Converter のシリアライズ実装
まずは POCO から JSON にシリアライズする処理を実装します。実装方法はシンプルで、 Utf8JsonWriter
を使って JSON の構造を記述していきます。文字列を1つ書き込むだけなので難しいことはせず、 null or not で処理がちょっと違うだけです。
public override void Write(Utf8JsonWriter writer, LinkObject value, JsonSerializerOptions options) { if (value.Value == null) { writer.WriteNullValue(); return; } writer.WriteStringValue(value.Value); }
ちょっと分からなかったのは null の除外方法です。 JsonSerializerOptions
で IgnoreNull
を指定すると null を JSON に出力させない設定が可能ですが、 Converter に入ってきた時点でプロパティのキーは書き込まれてしまっているため、 オブジェクトは null ではないが状態としては null の場合は null を書き込まざるをえませんでした。
Converter のデシリアライズ実装
続いて JSON から POCO へのデシリアライズ処理を実装します。 Utf8JsonReader
はいわゆる? PullParser 形式で、 JSON の構成要素を一つ一つ順番に読んでいきます。その時点でのトークンを見ながらオブジェクトにマッピングしていきます。
public override LinkObject Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { // objectスコープに入っていることをチェック if (reader.TokenType != JsonTokenType.StartObject) { throw new JsonException("Invalid TokenType"); } // Readを繰り返してobjectスコープの終わりまで読む var obj = new LinkObject(); while (reader.Read()) { if (reader.TokenType == JsonTokenType.EndObject) { return obj; } if (reader.TokenType != JsonTokenType.PropertyName) { continue; } var propName = reader.GetString().ToLower(); if (!reader.Read()) { throw new JsonException(); } var value = reader.TokenType switch { JsonTokenType.Null => null, JsonTokenType.String => reader.GetString(), _ => throw new JsonException() }; switch (propName) { case "value": obj.Value = value; break; case "link": obj.Link = value; break; default: throw new JsonException("Unknown property name"); } } throw new JsonException(); }