JavaライブラリJackson完全ガイド:JSON操作をマスターする

この記事から得られる知識

  • 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のキー名が異なる場合や、特定のフィールドを変換対象から除外したい場合などに柔軟に対応できます。

アノテーション説明
@JsonPropertyJSONのプロパティ名を指定します。Javaのフィールド名とJSONのキー名が異なる場合に非常に便利です。
@JsonIgnore特定のフィールドをシリアライズおよびデシリアライズの対象から除外します。パスワードのような機密情報を含めたくない場合などに使用します。
@JsonIncludeシリアライズ時に、nullや空のコレクションなど、特定の条件を持つフィールドを出力に含めるかどうかを制御します。
@JsonFormatDate型や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());
}
TypeReferenceは、ジェネリクスを含む複雑なデータ構造をデシリアライズする際の必須テクニックです。必ず覚えておきましょう。

ツリーモデル (`JsonNode`) の活用

これまではJSONとJavaオブジェクトを直接マッピングする方法(データバインディング)を見てきました。しかし、JSONの構造が動的に変わる場合や、一部のフィールドだけを柔軟に読み取りたい場合があります。

このようなケースでは、JSONをツリー構造としてメモリ上に展開するツリーモデルが役立ちます。その中心となるのがcom.fasterxml.jackson.databind.JsonNodeクラスです。

`JsonNode` の基本的な使い方

ObjectMapperreadTree()メソッドで、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など)が返るため、より安全に処理を記述できます。
ネストが深いJSONを扱う際は、NullPointerExceptionを避けるためにpath()メソッドを積極的に利用するのが良いプラクティスです。

モジュールの活用: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` の登録

ObjectMapperJavaTimeModuleを登録するだけで、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処理の効率化と品質向上に繋がれば幸いです。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です