JSONをC#に変換
「JSONをC#に変換」は、サンプルのドキュメントからクラスやレコードをブラウザ内で生成します。プロパティ名はPascalCaseになり、すべてのプロパティに元のJSONキーを載せた明示的な属性が付きます。System.Text.Jsonはデフォルトで大文字小文字を区別するため、指示しない限り、PascalCaseのプロパティはsnake_caseのキーにバインドされないからです。
ドキュメントが途中で終わっています。切れている可能性があります。
^
JSONをC#に変換はどう動きますか?
C#は、null許容参照型の設定によって「正しい出力」の形が変わる生成先です。そのため、このジェネレーターはこれを書式の好みではなく、第一級のオプションとして扱います。
null許容参照型がすべてのメンバーの形を決める
<Nullable>enable</Nullable>(.NET 6以降のすべてのテンプレートでのデフォルト)の下では、JSONで欠落しうるstringのプロパティは、いずれ警告を生む種であり、警告を無視すればNullReferenceExceptionになります。そのため、省略可能なフィールドとnullになり得るフィールドには?を付けます。値型ではNullable<T>になり、参照型ではnull許容の注釈になります。どちらもT?という書き方になる点は、この言語の気の利いた部分のひとつです。
null非許容のメンバーには初期化が必要で、なければCS8618が全メンバーで発生する
必須のstring Name { get; set; }は、初期化されていないという警告を出します。コンパイラには、デシリアライザーが値を入れてくれることが見えないからです。生成コードの定番の答えは= null!;です。何か別のものが代入する、というコンパイラへの明示的な約束です。これがないと、生成されたファイルはプロパティごとに1つずつ警告を出します。多くの人はnull許容をオフにしてこれを解決しますが、それではnull許容を使う理由そのものが捨てられます。
属性は慣習ではなく、明示的に書く
System.Text.Jsonは、設定で変えない限りプロパティ名を大文字小文字を区別して照合します。そのためUserIdはuser_idにバインドされず、黙ってnullのままです。そこで、元のキーを正確に持つ[JsonPropertyName](Newtonsoftなら[JsonProperty])をすべてのプロパティに付けます。そうすれば、シリアライザーのオプションが何であれ、クラスは正しく動きます。
クラスかレコードか
デシリアライズするペイロードには、レコードのほうが良いデフォルトです。値の等価性と読みやすいToStringが無料で手に入り、DTOは届いた後に変更すべきではありません。それでもセッター付きの可変なクラスを期待するコードは今も多いので、どちらも選べるようにしています。
intでは収まらない値はlongに
intは32ビットです。2,147,483,647を超えるサンプル値——スノーフレークIDやミリ秒のタイムスタンプ——はlongとして出力します。切り詰められたIDは、本物のデータが届き始めて初めて現れるバグだからです。
サンプル
[
{ "user_id": 900719925474099, "name": "Ada" },
{ "user_id": 12, "name": "Grace", "team": "ops" }
]生成結果
public class User
{
[JsonPropertyName("user_id")]
public long UserId { get; set; }
[JsonPropertyName("name")]
public string Name { get; set; } = null!;
[JsonPropertyName("team")]
public string? Team { get; set; }
}JSONをC#に変換が対応するオプションとエッジケースは?
| パラメータ | 型 | デフォルト | 動作とエッジケース |
|---|---|---|---|
| 形状 | class / record | class | recordは値の等価性と役立つToStringを提供し、DTOに適しています。classは変更可能なままを維持し、古いコードが期待するものです。 |
| 名前空間 | 識別子 | Generated | 出力を名前空間ブロックでラップします。既に名前空間があるファイルには、空のままにして型を直接出力します。 |
| System.Text.Json | 真偽値 | on | [JsonPropertyName]と対応するusingを出力します。.NET Frameworkで使用されるものなど、[JsonProperty]とNewtonsoft.Jsonを使用する場合はオフにします。 |
| nullable | 真偽値 | on | #nullable enableを出力し、オプションなメンバーをT?としてマークし、必須の参照型メンバーを=null!で初期化して、すべてのプロパティでCS8618が発生しないようにします。 |
| string | C#型 | string | 検出された形式はDateTimeやGuidにマッピングされません。手動で型を変更すればSystem.Text.JsonはISO 8601をDateTimeとして解析しますが、日付の形式が異なるレコードでは例外がスローされます。 |
| integer | C#型 | int / long | サンプルのいずれかが2,147,483,647を超える場合はlongになります。JSONの数値には宣言された幅がないため、この値は値から読み取られます。 |
| array | C#型 | List<T> | T[]ではなくList<T>を使用します。両方のシリアライザが対応しており、呼び出し側のコードが通常要求するものだからです。混合型の配列はList<object>になります。 |
| ネストしたオブジェクト | クラス | 固有の型 | 独自のクラスに昇格し、それを使用する型の前に宣言されます。同じ形状のオブジェクトは、Address、Address2、Address3を生成するのではなく、1つのクラスを共有します。 |
よくある質問
キーがすでにPascalCaseなのに、すべてのプロパティに属性が付くのはなぜですか?
バインドが保証されるのは、属性を付けたときだけだからです。System.Text.Jsonは、PropertyNameCaseInsensitiveを設定しない限り大文字小文字を区別して照合しますし、設定したとしてもuser_idとUserIdを結び付けられません。それには命名ポリシーが必要ですが、生成されたクラスは、それが設定されているとは前提できません。明示的な属性があれば、どんなシリアライザーオプションのもとでもクラスは正しく動きます。その1行の価値はあります。
「= null!」とは何ですか?削除してもよいですか?
コンパイラに、null非許容の参照プロパティが、自分の見えない何か(デシリアライザー)によって代入されることを伝え、CS8618を抑制するものです。プロパティをrequired(C# 11)にするか、コンストラクターで初期化すれば削除できます。やってはいけないのは、警告を消すためにnull許容参照型をオフにすることです。ありがちな反応ですが、本物のnullを捕捉できたはずの機能を捨てることになります。
レコードとクラスのどちらを使うべきですか?
デシリアライズして読むだけのペイロードにはレコードです。値の等価性のおかげでテストが格段に書きやすくなり、外部から届いたデータには不変であることが適切です。オブジェクトを変更する予定がある場合、C# 9より古いフレームワークの場合、既存のコードベースがあらゆる場所でセッター付きプロパティを前提としている場合はクラスにしてください。
日付がstringになるのはなぜですか?
サンプルからDateTimeを生成すると、日付の書式が異なる最初のレコードで例外を投げるクラスができるからです。System.Text.Jsonが受け付けるのはISO 8601だけですが、実際のAPIはUnixタイムスタンプや米国形式の日付、コロンなしのオフセットなどを送ってきます。検出された書式は注記されるので、意図して型を変更できます。書式が必要とするなら、コンバーターを付けてください。
NewtonsoftとSystem.Text.Json、どちらにすべきですか?
モダンな.NETで動くものならSystem.Text.Jsonです。標準で含まれており、大幅に速く、改善の投資先もそこです。.NET Frameworkの場合や、System.Text.Jsonがまだ対応していないもの——TypeNameHandling、カスタムのコントラクトリゾルバー、壊れた入力へのより寛容なパース——に依存している場合はNewtonsoftです。どちらを選んでも、ジェネレーターは正しい属性を出力します。
JSONはアップロードされますか?
いいえ。ドキュメントのパース、型の推論、コードの生成は、すでに開いているタブの中で行われます。Networkパネルで確認できます。理由はいつも同じです。クラスジェネレーターに貼り付けられるサンプルは実際のレスポンスであり、顧客データが入ったままのことも多いからです。