この記事から得られる知識
- Jacksonの基本的な役割と、なぜJava開発で重要なのかがわかります。
- JavaオブジェクトとJSON文字列を相互に変換(シリアライズ・デシリアライズ)する方法を習得できます。
@JsonProperty
や@JsonIgnore
など、よく使われるアノテーションによる柔軟なマッピング制御を学べます。ObjectMapper
の高度な設定を通じて、未知のプロパティを無視したり、出力を整形したりといったカスタマイズが可能になります。- リストやマップなどのコレクション型や、ジェネリクスを使った複雑なデータ構造の扱い方を理解できます。
- JSONの構造が固定されていない場合に役立つツリーモデル(
JsonNode
)の操作方法を学べます。 - Java 8以降の日時API(
java.time
)を正しく扱うためのモジュールの使い方をマスターできます。
はじめに:Jacksonとは何か?
Jacksonは、JavaアプリケーションでJSON(JavaScript Object Notation)形式のデータを扱うための、非常に強力で人気のあるライブラリです。 多くのJavaフレームワーク、例えばSpring Frameworkなどで標準的に採用されており、JavaにおけるJSON処理のデファクトスタンダードと見なされています。
主な機能は、Javaオブジェクト(POJO – Plain Old Java Object)とJSON文字列との間の変換です。 この変換処理には2つの方向があります。
- シリアライズ(Serialization): JavaオブジェクトをJSON文字列に変換すること。
- デシリアライズ(Deserialization): JSON文字列をJavaオブジェクトに変換すること。
Jacksonを使えば、これらの処理を数行のコードで直感的に実装でき、Web API開発や設定ファイルの読み書き、データ交換など、現代のアプリケーション開発における様々な場面で絶大な効果を発揮します。 高速で軽量、かつ豊富なアノテーションや設定オプションによる高い柔軟性が特徴です。
基本的な使い方:POJOとJSONの相互変換
Jacksonの心臓部となるのがcom.fasterxml.jackson.databind.ObjectMapper
クラスです。このクラスが、シリアライズとデシリアライズの魔法を実現します。
1. 依存関係の追加
まず、プロジェクトにJacksonの依存関係を追加する必要があります。Mavenを使用している場合は、pom.xml
に以下を追記します。
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.2</version> <!-- 最新バージョンを確認してください -->
</dependency>
Gradleの場合はbuild.gradle
に以下を追加します。
implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.2' // 最新バージョンを確認してください
2. シリアライズ:JavaオブジェクトからJSONへ
writeValueAsString()
メソッドを使用して、JavaオブジェクトをJSON文字列に変換します。
対象となるJavaクラス (POJO):
public class User {
private int id;
private String name;
private String email;
// GetterとSetter、コンストラクタは省略
}
変換コード:
import com.fasterxml.jackson.databind.ObjectMapper;
public class Main {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
User user = new User(1, "Taro Yamada", "taro.yamada@example.com");
// JavaオブジェクトをJSON文字列にシリアライズ
String jsonString = mapper.writeValueAsString(user);
System.out.println(jsonString);
// 出力: {"id":1,"name":"Taro Yamada","email":"taro.yamada@example.com"}
}
}
3. デシリアライズ:JSONからJavaオブジェクトへ
逆に、JSON文字列からJavaオブジェクトを復元するにはreadValue()
メソッドを使用します。
変換コード:
import com.fasterxml.jackson.databind.ObjectMapper;
public class Main {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
String jsonString = "{\"id\":2,\"name\":\"Hanako Tanaka\",\"email\":\"hanako.tanaka@example.com\"}";
// JSON文字列をUserオブジェクトにデシリアライズ
User user = mapper.readValue(jsonString, User.class);
System.out.println("ID: " + user.getId()); // 出力: ID: 2
System.out.println("Name: " + user.getName()); // 出力: Name: Hanako Tanaka
System.out.println("Email: " + user.getEmail()); // 出力: Email: hanako.tanaka@example.com
}
}
アノテーションによる柔軟なマッピング制御
Jacksonの真価は、アノテーションを使った柔軟なカスタマイズにあります。 これにより、Javaのフィールド名とJSONのキー名が異なる場合や、特定のフィールドを変換対象から除外したい場合などに柔軟に対応できます。
アノテーション | 説明 |
---|---|
@JsonProperty |
JSONのプロパティ名を指定します。Javaのフィールド名とJSONのキー名が異なる場合に非常に便利です。 |
@JsonIgnore |
特定のフィールドをシリアライズおよびデシリアライズの対象から除外します。パスワードのような機密情報を含めたくない場合などに使用します。 |
@JsonInclude |
シリアライズ時に、nullや空のコレクションなど、特定の条件を持つフィールドを出力に含めるかどうかを制御します。 |
@JsonFormat |
Date型やJava 8の日時APIのフォーマットを指定します。 例えば、”yyyy-MM-dd HH:mm:ss”のような形式で日時を表現できます。 |
@JsonCreator と @JsonProperty |
デシリアライズ時に使用するコンストラクタやファクトリメソッドを指定します。 イミュータブル(不変)なオブジェクトを扱う際に必須です。 |
@JsonValue |
オブジェクト全体を単一の値としてシリアライズする方法を指定します。 Enumをその文字列表現でシリアライズしたい場合などによく使われます。 |
@JsonPropertyOrder |
シリアライズ時のプロパティの出力順序を指定します。 |
@JsonUnwrapped |
ネストされたオブジェクトを展開して、親オブジェクトのプロパティとしてフラットにシリアライズします。 |
アノテーション使用例:
import com.fasterxml.jackson.annotation.*;
import java.util.Date;
@JsonInclude(JsonInclude.Include.NON_NULL) // nullのフィールドはJSONに出力しない
@JsonPropertyOrder({ "user_id", "full_name", "email" }) // JSON出力時の順序を指定
public class AnnotatedUser {
@JsonProperty("user_id") // JSONでのキー名を "user_id" にする
private int id;
@JsonProperty("full_name")
private String name;
private String email; // アノテーションなしの場合はフィールド名がそのまま使われる
@JsonIgnore // このフィールドはJSONに含めない
private String password;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ")
private Date createdAt;
// デシリアライズ用のコンストラクタ
@JsonCreator
public AnnotatedUser(@JsonProperty("user_id") int id, @JsonProperty("full_name") String name) {
this.id = id;
this.name = name;
}
// GetterとSetter...
}
ObjectMapperの高度な設定
ObjectMapper
は、アノテーションだけでなく、直接メソッドを呼び出すことで動作を詳細に設定できます。 これらの設定は、アプリケーション全体で一貫したJSON処理ルールを適用したい場合に役立ちます。
SerializationFeature
シリアライズ(Javaオブジェクト -> JSON)の挙動を制御します。
-
INDENT_OUTPUT
出力されるJSONを人間が読みやすいように整形(インデント)します。 デバッグ時に非常に便利です。
mapper.enable(SerializationFeature.INDENT_OUTPUT);
-
FAIL_ON_EMPTY_BEANS
シリアライズ可能なプロパティが一つも見つからないBeanを処理しようとした場合に、例外をスローするかどうかを設定します。 デフォルトは
true
(例外をスロー)です。mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); // 空のBeanを {} として出力
-
WRITE_DATES_AS_TIMESTAMPS
Date型を数値のタイムスタンプ(エポックからのミリ秒)として出力するか、ISO-8601形式の文字列として出力するかを決めます。デフォルトは
true
(タイムスタンプ)です。mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); // 日付を "1970-01-01T00:00:00.000+00:00" のような文字列で出力
DeserializationFeature
デシリアライズ(JSON -> Javaオブジェクト)の挙動を制御します。
-
FAIL_ON_UNKNOWN_PROPERTIES
JSONには存在するがJavaオブジェクトには対応するフィールドがないプロパティに遭遇した場合に、例外をスローするかどうかを設定します。 APIのバージョンアップでプロパティが追加された場合など、予期せぬプロパティを安全に無視するために
false
に設定することが多いです。mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); // 未知のプロパティを無視する
設定を適用した例:
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.DeserializationFeature;
public class ConfiguredMapper {
public static ObjectMapper create() {
ObjectMapper mapper = new ObjectMapper();
// --- シリアライズ設定 ---
// JSONを読みやすくインデントする
mapper.enable(SerializationFeature.INDENT_OUTPUT);
// Date型をタイムスタンプではなくISO-8601形式の文字列で出力
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
// --- デシリアライズ設定 ---
// JSONにあってPOJOにないプロパティを無視する
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return mapper;
}
}
コレクションとジェネリクスの扱い
実世界のアプリケーションでは、リストやマップのようなコレクションを扱うことが頻繁にあります。Jacksonはこれらも簡単に処理できますが、ジェネリクスが絡むと少し注意が必要です。
リストやマップの変換
List<User>
やMap<String, User>
のようなコレクションは、特別な設定なしで直感的に変換できます。
List<User> userList = new ArrayList<>();
userList.add(new User(1, "User 1", "user1@test.com"));
userList.add(new User(2, "User 2", "user2@test.com"));
// シリアライズ
String jsonArray = mapper.writeValueAsString(userList);
// 出力: [{"id":1,"name":"User 1","email":"user1@test.com"},{"id":2,"name":"User 2","email":"user2@test.com"}]
// デシリアライズ (この方法はジェネリクスではうまく機能しない)
// List<User> deserializedList = mapper.readValue(jsonArray, List.class);
// これだとListの中身がLinkedHashMapになってしまい、ClassCastExceptionが発生する
ジェネリクスのための `TypeReference`
上記のデシリアライズの問題は、Javaの型消去(Type Erasure)が原因です。実行時にはList<User>
のUser
という情報が失われてしまうため、Jacksonは何の型に変換すればよいか判断できません。
この問題を解決するのがcom.fasterxml.jackson.core.type.TypeReference
です。 これを使うことで、実行時にもジェネリクスの型情報を保持し、正しくデシリアライズできます。
import com.fasterxml.jackson.core.type.TypeReference;
import java.util.List;
// ...
String jsonArray = "[{\"id\":1,\"name\":\"User 1\"},{\"id\":2,\"name\":\"User 2\"}]";
// TypeReferenceを使ってジェネリクスの型情報を渡す
List<User> userList = mapper.readValue(jsonArray, new TypeReference<List<User>>() {});
for (User user : userList) {
System.out.println(user.getName());
}
ツリーモデル (`JsonNode`) の活用
これまではJSONとJavaオブジェクトを直接マッピングする方法(データバインディング)を見てきました。しかし、JSONの構造が動的に変わる場合や、一部のフィールドだけを柔軟に読み取りたい場合があります。
このようなケースでは、JSONをツリー構造としてメモリ上に展開するツリーモデルが役立ちます。その中心となるのがcom.fasterxml.jackson.databind.JsonNode
クラスです。
`JsonNode` の基本的な使い方
ObjectMapper
のreadTree()
メソッドで、JSON文字列をJsonNode
オブジェクトにパースします。
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JsonNodeExample {
public static void main(String[] args) throws Exception {
String jsonString = "{"
+ "\"name\": \"Taro Yamada\","
+ "\"age\": 30,"
+ "\"isMember\": true,"
+ "\"address\": {"
+ " \"street\": \"1-2-3 Marunouchi\","
+ " \"city\": \"Tokyo\""
+ "},"
+ "\"skills\": [\"Java\", \"SQL\", \"Spring\"]"
+ "}";
ObjectMapper mapper = new ObjectMapper();
JsonNode rootNode = mapper.readTree(jsonString);
// フィールドへのアクセス
String name = rootNode.get("name").asText();
int age = rootNode.get("age").asInt();
boolean isMember = rootNode.get("isMember").asBoolean();
System.out.println("Name: " + name); // Name: Taro Yamada
// ネストしたオブジェクトへのアクセス
JsonNode addressNode = rootNode.path("address");
String city = addressNode.get("city").asText();
System.out.println("City: " + city); // City: Tokyo
// 配列へのアクセス
JsonNode skillsNode = rootNode.path("skills");
if (skillsNode.isArray()) {
for (JsonNode skill : skillsNode) {
System.out.println("Skill: " + skill.asText());
}
}
}
}
`get()` と `path()` の違い
JsonNode
で値を取得するメソッドにはget()
とpath()
があります。両者の違いは、存在しないノードにアクセスしようとしたときの挙動です。
get(key)
: 指定したキーが存在しない場合、null
を返します。そのため、メソッドチェーンを続けるとNullPointerException
が発生する可能性があります。path(key)
: 指定したキーが存在しない場合、特殊な「MissingNode」を返します。MissingNodeに対してさらにメソッドを呼び出してもNullPointerException
は発生せず、デフォルト値(数値なら0、文字列ならnullなど)が返るため、より安全に処理を記述できます。
モジュールの活用:Java 8 日時APIへの対応
Jacksonのコア機能だけでは、Java 8で導入された新しい日時API(java.time
パッケージのLocalDate
, LocalDateTime
など)を正しく扱うことができません。 これらをシリアライズしようとすると、意図しない複雑なオブジェクト構造が出力されてしまいます。
この問題は、Jacksonモジュールを追加することで解決できます。 Java 8の日時APIに対応するには、jackson-datatype-jsr310
モジュールが必要です。
1. 依存関係の追加
Mavenのpom.xml
に以下を追加します。
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.17.2</version> <!-- jackson-databindと同じバージョン -->
</dependency>
2. `JavaTimeModule` の登録
ObjectMapper
にJavaTimeModule
を登録するだけで、LocalDateTime
などがデフォルトでISO-8601形式(例: “2025-07-14T10:00:00″)の文字列として扱われるようになります。
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import java.time.LocalDateTime;
public class Java8TimeExample {
public static class Event {
public String name;
public LocalDateTime eventDate;
}
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
// JavaTimeModuleを登録
mapper.registerModule(new JavaTimeModule());
// Date/Timeをタイムスタンプ形式で出力しないようにする設定も忘れずに
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
Event event = new Event();
event.name = "Tech Conference";
event.eventDate = LocalDateTime.of(2025, 10, 20, 9, 30);
String json = mapper.writeValueAsString(event);
System.out.println(json);
// 出力例: {"name":"Tech Conference","eventDate":"2025-10-20T09:30:00"}
}
}
もちろん、@JsonFormat
アノテーションを使えば、個別のプロパティに対してデフォルトとは異なるフォーマットを指定することも可能です。
public class Event {
public String name;
@JsonFormat(pattern = "yyyy/MM/dd HH:mm")
public LocalDateTime eventDate;
}
まとめ
この記事では、JavaにおけるJSON処理の標準ライブラリであるJacksonの包括的な使い方を解説しました。基本的なPOJOとJSONの相互変換から始まり、アノテーションによる柔軟なマッピング、ObjectMapper
の詳細設定、コレクションやジェネリクスの扱い、そしてツリーモデルやモジュールの活用まで、幅広いトピックをカバーしました。
Jacksonは非常に多機能で奥が深いライブラリですが、今回紹介した内容をマスターすれば、ほとんどの日常的な開発シーンでJSONを自在に操ることができるようになるはずです。特に、以下のポイントは重要です。
ObjectMapper
がすべての操作の基本。- アノテーションを使いこなすことで、コードをクリーンに保ちつつ複雑な要件に対応できる。
- ジェネリクスを扱う際は
TypeReference
が必須。 - 未知のプロパティを安全に扱うために
FAIL_ON_UNKNOWN_PROPERTIES
の設定を検討する。 - Java 8以降の日時型には
JavaTimeModule
を登録する。
このガイドが、皆さんのJava開発におけるJSON処理の効率化と品質向上に繋がれば幸いです。