JSONをJavaに変換
「JSONをJavaに変換」は、サンプルのドキュメントからレコードまたはPOJOをブラウザ内で生成します。省略可能な数値フィールドはボクシングされます——intではなくIntegerです。プリミティブはnullを保持できないため、存在しないキーは0にデシリアライズされ、本物のゼロと区別がつかなくなるからです。
ドキュメントが途中で終わっています。切れている可能性があります。
^
JSONをJavaに変換はどう動きますか?
Javaのプリミティブ型は、省略可能フィールドの問題をスタイルの好みではなく具体的な問題にします。間違えると、本当に見つけにくいバグになります。
プリミティブは「存在しない」を表せない
int countは、存在しないcountキーを0にデシリアライズします。例外も警告もありません。そしてその後では、カウントが一度も送られてこなかったレコードと、カウントが本当にゼロだったレコードを区別する方法がありません。booleanはfalseに、doubleは0.0になっても同じです。そのため、省略可能なフィールドとnullになり得るフィールドはボクシングします。Integer、Long、Double、Booleanはいずれもnullを保持でき、この問いを立てられるようにします。
ジェネリクスはプリミティブを一切保持できない
List<int>はコンパイルできません。リストの要素は、省略可能かどうかに関係なくすべてボクシングされます。整数の配列がList<Integer>になるのはこのためです。
レコードかPOJOか
Java 17以降なら、レコードのほうが良いデフォルトです。不変で、ボイラープレートがなく、equals、hashCode、toStringが無料で付き、Jacksonも2.12から対応しています。privateフィールドとgetter、setterを持つPOJOが欲しくなるのは、Java 8のコードベースや、引数なしコンストラクターを要求するフレームワークを使っている場合です。どちらも用意されています。
Jacksonのアノテーションが元のキーを持つ
Javaの慣習はcamelCaseですが、JSONのキーはたいてい違います。どこか別の場所でPropertyNamingStrategyが設定されていることに頼る代わりに、すべてのコンポーネントに、ドキュメントから取った正確なキーを持つ明示的な@JsonPropertyを付けます。こうすれば、ObjectMapperの設定が何であれ、クラスは正しくバインドされます。
1ファイルにつきpublicな型は1つ
Javaでは、ソースファイルごとにpublicなトップレベル型はちょうど1つしか許されません。複数の型を生成するドキュメントは、1回の貼り付けではコンパイルできません。出力はヘッダーコメントでその旨を伝えます。各型を<TypeName>.javaとして保存するか、1つの外側のクラスの中にネストしてください。javacが拒否するファイルを、それとは書かずに出力するのは、10分を無駄にするタイプの小さな不誠実です。
サンプル
[
{ "user_id": 1, "retries": 0, "tags": ["a"] },
{ "user_id": 2, "tags": [] }
]生成結果
public record User(
@JsonProperty("user_id") int userId,
@JsonProperty("retries") Integer retries,
@JsonProperty("tags") List<String> tags
) {}JSONをJavaに変換が対応するオプションとエッジケースは?
| パラメータ | 型 | デフォルト | 動作とエッジケース |
|---|---|---|---|
| 形状 | record / POJO | record | Java 17以上では、定型コードなしで不変DTOを作成するためのrecordを使用します。Java 8、または引数なしのコンストラクタとセッターが必要なものにはPOJOを使用します。 |
| パッケージ | 識別子 | (なし) | パッケージ宣言を追加します。スケッチファイルの場合は空のままにしてください。ファイルが配置されるディレクトリに一致するように設定します。 |
| Jackson | 真偽値 | on | 正確なJSONキーで@JsonPropertyを出力し、バインディングが他の場所で構成されたObjectMapperの命名戦略に依存しないようにします。 |
| Optionalをボックス化 | 真偽値 | on | OptionalのintはIntegerになります。これをオフにすると、キーが存在しない場合のデシリアライズ結果が0となり、実際のゼロと区別できなくなります。 |
| string | Java型 | String | 検出されたフォーマットはInstant、LocalDateまたはUUIDにマッピングされません。Jacksonは最初の2つにはJavaTimeModuleの登録が必要であり、フォーマットが異なるレコードがある場合は例外をスローします。 |
| integer | Java型 | int / long | サンプルのいずれかが2,147,483,647を超える場合はlongになります。JSONの数値には宣言された幅がないため、幅は値から読み取られます。 |
| array | Java型 | List<T> | ジェネリックスがプリミティブを保持できないため、要素は常にボックス化されます。混合配列はList<Object>になります。 |
| ネストしたオブジェクト | 型 | 固有の型 | それを使用する型の前に宣言される、独自のレコードまたはクラスに昇格されます。同一の形状は、番号付きの重複を生成する代わりに1つの型を共有します。 |
よくある質問
あるフィールドはintで、別のフィールドはIntegerなのはなぜですか?
そのIntegerは、あるレコードには現れ、別のレコードには現れなかったからです。プリミティブのintはnullを保持できないので、Jacksonは存在しないキーを0にデシリアライズします。そしてその後の処理では、それと、本当にゼロを送ってきたレコードを区別する方法がありません。Integerならnullになれるので、欠落が見えます。全レコードに存在するフィールドはプリミティブのままです。そのほうが安価でシンプルだからです。
出力全体を1つのファイルに貼り付けても大丈夫ですか?
複数の型を生成した場合はできません。Javaではソースファイルごとにpublicなトップレベル型は1つしか許されないので、javacが拒否します。各型を<TypeName>.javaとして保存するか、ネストしている型を1つの外側のクラスの中のstaticネストクラスにしてください。どちらのケースに当たるかは、出力の冒頭に書かれています。
レコードとPOJO、どちらを使うべきですか?
Java 17以降で、オブジェクトがどこかから届いたデータであるならレコードです。不変で、equals、hashCode、toStringが正しく提供され、Jacksonも2.12からレコードに対応しています。Java 8の場合、フレームワークが引数なしコンストラクターとセッターを要求する場合(古いJPA、一部のSpringの構成)、またはオブジェクトを構築後に本当に変更する必要がある場合はPOJOです。
タイムスタンプがInstantではなくStringになるのはなぜですか?
Jacksonは、JavaTimeModuleを登録しない限りInstantにパースできず、しかも期待した書式と違う最初のレコードで例外を投げるからです。実際のAPIは、Unix秒、Unixミリ秒、オフセットありとなしのISO 8601、そしてときに地域固有の形式を送ってきます。検出された書式は上のリファレンスに注記してあります。意図して型を変更し、書式が必要とするならコンバーターを追加してください。
Lombokは必要ですか?
不要です。ジェネレーターは意図的に@Dataや@Builderを出力しません。Java 17以降では、recordがLombokの@Valueが担っていた役割を、ビルドにアノテーションプロセッサーを足さずに果たします。POJOの出力は本物のgetterとsetterを持つ素のJavaなので、どこでもコンパイルできます。プロジェクトですでにLombokを使っているなら、アクセッサーは自分で置き換えても構いませんが、このツールはそれを要求しません。
JSONはアップロードされますか?
いいえ。パース、型推論、コードの生成はすべてブラウザ内で実行されます。貼り付け中にNetworkパネルを開けば、何も外へ出ていかないのが分かります。クラスジェネレーターに貼り付けられるサンプルは、たいてい統合相手のシステムからの実際のレスポンスです。まさに、知らないサーバーを通すべきでないデータです。