Jackson
ライブラリ
Jackson
概要
JacksonはJavaの最も人気で高性能なJSONライブラリです。Spring Bootのデフォルトライブラリとして採用され、JSON、XML、YAMLの処理に対応し、エンタープライズ級の機能を提供します。豊富なアノテーション、柔軟な設定オプション、ストリーミングAPI、Tree Model、データバインディングの3つのアプローチによる多様な使用シナリオに対応し、Java エンタープライズアプリケーションの標準的な選択肢として確固たる地位を築いています。
詳細
Jackson 2.19.0は、Spring Boot エコシステムでの採用により大規模ファイル処理での優秀なパフォーマンスが評価されている成熟したライブラリです。jackson-core(ストリーミングAPI)、jackson-databind(データバインディング)、jackson-annotations(アノテーション)の3つのモジュールから構成され、オブジェクトマッパーによる高レベルAPI、Builder パターンサポート、カスタムシリアライザー/デシリアライザーなど、エンタープライズ開発で必要とされる包括的な機能を提供します。
主な特徴
- 包括的フォーマット対応: JSON、XML、YAML、CSV等の統一処理
- 高性能: ストリーミングAPIによる低メモリフットプリント
- 豊富なアノテーション: @JsonProperty、@JsonIgnore、@JsonCreator等
- 柔軟な設定: SerializationFeature、DeserializationFeatureによる細かな制御
- Tree Model: JsonNodeによるDOM風のJSON操作
- エンタープライズ級: Spring Boot等のフレームワークとの深い統合
メリット・デメリット
メリット
- Spring Boot等の主要Javaフレームワークでのデフォルト採用による高い信頼性
- 豊富なアノテーションと設定オプションによる柔軟なカスタマイズ
- ストリーミングAPI、データバインディング、Tree Modelの3つのアプローチ
- 大規模データ処理での優秀なパフォーマンスと低メモリ消費
- 10年以上の開発実績と活発なコミュニティサポート
- JSON以外にもXML、YAML等の形式をサポート
デメリット
- 豊富な機能ゆえの学習コストの高さと複雑な設定
- 基本的な用途には機能過多でオーバースペックな場合がある
- 依存関係が多く、アプリケーションサイズが増加する可能性
- アノテーションの多用によりコードの可読性が低下する場合がある
- 一部の高度な機能は Java リフレクションに依存しパフォーマンスに影響
- 設定ミスによる実行時エラーが発生しやすい
参考ページ
書き方の例
基本的なセットアップ
<!-- Maven依存関係 -->
<properties>
<jackson.version>2.19.0</jackson.version>
</properties>
<dependencies>
<!-- データバインディング(コアとアノテーションを含む) -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- XML処理用 -->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- YAML処理用 -->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- Java 8 時間型サポート -->
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>${jackson.version}</version>
</dependency>
</dependencies>
// Gradle依存関係
dependencies {
implementation "com.fasterxml.jackson.core:jackson-databind:2.19.0"
implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.19.0"
implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.19.0"
implementation "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.19.0"
}
基本的なシリアライゼーション・デシリアライゼーション
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.type.TypeReference;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.List;
public class JacksonBasics {
// ObjectMapperの作成(一度作成して再利用推奨)
private static final ObjectMapper mapper = new ObjectMapper();
// 基本的なPOJO
public static class User {
public String name;
public int age;
public String email;
public boolean active;
// デフォルトコンストラクタ(必須)
public User() {}
public User(String name, int age, String email, boolean active) {
this.name = name;
this.age = age;
this.email = email;
this.active = active;
}
// getter/setter(オプション - パブリックフィールド使用の場合)
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public boolean isActive() { return active; }
public void setActive(boolean active) { this.active = active; }
}
public static void basicOperations() throws IOException {
// オブジェクトの作成
User user = new User("田中太郎", 30, "[email protected]", true);
// 1. オブジェクト → JSON文字列
String jsonString = mapper.writeValueAsString(user);
System.out.println("JSON文字列: " + jsonString);
// 2. オブジェクト → JSONファイル
mapper.writeValue(new File("user.json"), user);
// 3. オブジェクト → バイト配列
byte[] jsonBytes = mapper.writeValueAsBytes(user);
System.out.println("JSONバイト数: " + jsonBytes.length);
// 4. JSON文字列 → オブジェクト
String jsonInput = "{\"name\":\"佐藤花子\",\"age\":25,\"email\":\"[email protected]\",\"active\":true}";
User deserializedUser = mapper.readValue(jsonInput, User.class);
System.out.println("デシリアライズ: " + deserializedUser.getName());
// 5. ファイル → オブジェクト
User userFromFile = mapper.readValue(new File("user.json"), User.class);
System.out.println("ファイルから: " + userFromFile.getName());
// 6. URL → オブジェクト(REST API呼び出し)
// User userFromUrl = mapper.readValue(new URL("https://api.example.com/user/123"), User.class);
}
// ジェネリック型の処理
public static void genericTypes() throws IOException {
// Map型
Map<String, Integer> scoreMap = Map.of("国語", 85, "数学", 92, "英語", 78);
String mapJson = mapper.writeValueAsString(scoreMap);
Map<String, Integer> deserializedMap = mapper.readValue(mapJson,
new TypeReference<Map<String, Integer>>() {});
System.out.println("マップ: " + deserializedMap);
// List型
List<User> users = List.of(
new User("田中", 30, "[email protected]", true),
new User("佐藤", 25, "[email protected]", false)
);
String listJson = mapper.writeValueAsString(users);
List<User> deserializedUsers = mapper.readValue(listJson,
new TypeReference<List<User>>() {});
System.out.println("ユーザー数: " + deserializedUsers.size());
// 複雑なネストした型
Map<String, List<User>> departmentUsers = Map.of(
"開発部", List.of(new User("開発太郎", 28, "[email protected]", true)),
"営業部", List.of(new User("営業花子", 32, "[email protected]", true))
);
String complexJson = mapper.writeValueAsString(departmentUsers);
Map<String, List<User>> deserializedComplex = mapper.readValue(complexJson,
new TypeReference<Map<String, List<User>>>() {});
System.out.println("部署数: " + deserializedComplex.size());
}
}
アノテーションとカスタマイズ
import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import java.time.LocalDateTime;
import java.util.List;
// アノテーションを使用した高度なPOJO
@JsonIgnoreProperties(ignoreUnknown = true) // 未知のプロパティを無視
public class AdvancedUser {
@JsonProperty("user_id") // JSONプロパティ名の指定
private Long id;
@JsonProperty("full_name")
private String name;
@JsonAlias({"email_address", "mail"}) // 複数の別名をサポート
private String email;
@JsonIgnore // シリアライズ/デシリアライズで無視
private String password;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") // 日時フォーマット指定
private LocalDateTime createdAt;
@JsonInclude(JsonInclude.Include.NON_NULL) // null値を除外
private String description;
@JsonInclude(JsonInclude.Include.NON_EMPTY) // 空のコレクションを除外
private List<String> tags;
// 書き込み専用プロパティ(デシリアライズのみ)
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private String temporaryToken;
// 読み取り専用プロパティ(シリアライズのみ)
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
private String internalStatus;
// カスタムコンストラクタを使用したデシリアライゼーション
@JsonCreator
public AdvancedUser(
@JsonProperty("user_id") Long id,
@JsonProperty("full_name") String name,
@JsonProperty("email") String email) {
this.id = id;
this.name = name;
this.email = email;
this.createdAt = LocalDateTime.now();
}
// getter/setter
public Long getId() { return id; }
public String getName() { return name; }
public String getEmail() { return email; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public LocalDateTime getCreatedAt() { return createdAt; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public List<String> getTags() { return tags; }
public void setTags(List<String> tags) { this.tags = tags; }
public String getTemporaryToken() { return temporaryToken; }
public void setTemporaryToken(String temporaryToken) { this.temporaryToken = temporaryToken; }
public String getInternalStatus() { return internalStatus; }
public void setInternalStatus(String internalStatus) { this.internalStatus = internalStatus; }
}
// Builderパターンを使用したイミュータブルなクラス
@JsonDeserialize(builder = ImmutablePerson.Builder.class)
public class ImmutablePerson {
private final String name;
private final Integer age;
private final String email;
private ImmutablePerson(String name, Integer age, String email) {
this.name = name;
this.age = age;
this.email = email;
}
public String getName() { return name; }
public Integer getAge() { return age; }
public String getEmail() { return email; }
@JsonPOJOBuilder(buildMethodName = "create", withPrefix = "set")
public static class Builder {
@JsonProperty("known_as")
@JsonAlias({"identifier", "first_name"})
private String name;
private Integer age;
private String email;
public Builder setName(String name) {
this.name = name;
return this;
}
public Builder setAge(Integer age) {
this.age = age;
return this;
}
public Builder setEmail(String email) {
this.email = email;
return this;
}
public ImmutablePerson create() {
return new ImmutablePerson(name, age, email);
}
}
}
ObjectMapperの詳細設定
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class JacksonConfiguration {
public static ObjectMapper createConfiguredMapper() {
ObjectMapper mapper = new ObjectMapper();
// シリアライゼーション設定
mapper.enable(SerializationFeature.INDENT_OUTPUT); // 整形出力
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); // 日付を文字列で出力
mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); // 空のBeanでエラーにしない
// デシリアライゼーション設定
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); // 未知のプロパティでエラーにしない
mapper.enable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT); // 空文字をnullとして受け入れ
mapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY); // 単一値を配列として受け入れ
// パーサー設定
mapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true); // コメントを許可
mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true); // シングルクォートを許可
mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true); // フィールド名のクォート省略を許可
// ジェネレーター設定
mapper.configure(JsonGenerator.Feature.ESCAPE_NON_ASCII, false); // 非ASCII文字をエスケープしない
// Java 8 時間型サポート
mapper.registerModule(new JavaTimeModule());
// カスタムモジュールの登録
SimpleModule customModule = new SimpleModule();
customModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer());
customModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer());
mapper.registerModule(customModule);
return mapper;
}
// カスタムシリアライザー
public static class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Override
public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers)
throws IOException {
gen.writeString(value.format(formatter));
}
}
// カスタムデシリアライザー
public static class LocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Override
public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException {
return LocalDateTime.parse(p.getValueAsString(), formatter);
}
}
// 設定の使用例
public static void demonstrateConfiguration() throws IOException {
ObjectMapper mapper = createConfiguredMapper();
// 設定が適用されたマッパーの使用
AdvancedUser user = new AdvancedUser(1L, "設定テスト", "[email protected]");
user.setDescription(""); // 空文字
user.setTags(List.of("tag1", "tag2"));
String json = mapper.writeValueAsString(user);
System.out.println("設定済みJSON:\n" + json);
// 柔軟なJSONの解析(コメント、シングルクォート等を含む)
String flexibleJson = """
{
// これはコメントです
'user_id': 2,
'full_name': '柔軟解析',
email: '[email protected]', // クォートなしフィールド名
unknown_field: 'これは無視されます'
}
""";
AdvancedUser parsed = mapper.readValue(flexibleJson, AdvancedUser.class);
System.out.println("柔軟解析結果: " + parsed.getName());
}
}
Tree Model(JsonNode)による動的JSON処理
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
public class JacksonTreeModel {
private static final ObjectMapper mapper = new ObjectMapper();
public static void treeModelOperations() throws IOException {
// JSON文字列からJsonNodeを作成
String jsonString = """
{
"user": {
"name": "田中太郎",
"age": 30,
"contact": {
"email": "[email protected]",
"phone": "090-1234-5678"
},
"hobbies": ["読書", "映画鑑賞", "プログラミング"]
},
"metadata": {
"created": "2025-01-01",
"version": "1.0"
}
}
""";
JsonNode root = mapper.readTree(jsonString);
// 値の読み取り
String name = root.get("user").get("name").asText();
int age = root.get("user").get("age").asInt();
String email = root.get("user").get("contact").get("email").asText();
System.out.println("名前: " + name + ", 年齢: " + age + ", メール: " + email);
// 深いパスでの値取得(JSON Pointer使用)
String phone = root.at("/user/contact/phone").asText();
String version = root.at("/metadata/version").asText();
System.out.println("電話: " + phone + ", バージョン: " + version);
// 配列の処理
JsonNode hobbies = root.get("user").get("hobbies");
if (hobbies.isArray()) {
System.out.println("趣味:");
for (JsonNode hobby : hobbies) {
System.out.println(" - " + hobby.asText());
}
}
// フィールドの存在チェック
if (root.has("user") && root.get("user").has("contact")) {
System.out.println("連絡先情報が存在します");
}
// nullチェック
JsonNode description = root.get("user").get("description");
if (description == null || description.isNull()) {
System.out.println("説明フィールドは存在しないかnullです");
}
}
public static void modifyJsonTree() throws IOException {
// 新しいJSONオブジェクトの作成
ObjectNode root = mapper.createObjectNode();
// 基本的な値の設定
root.put("name", "新規ユーザー");
root.put("age", 25);
root.put("active", true);
root.put("score", 95.5);
// ネストしたオブジェクトの作成
ObjectNode contact = root.putObject("contact");
contact.put("email", "[email protected]");
contact.put("phone", "090-9876-5432");
// 配列の作成
ArrayNode skills = root.putArray("skills");
skills.add("Java");
skills.add("Spring Boot");
skills.add("JSON");
// 既存オブジェクトの追加
User existingUser = new User("既存ユーザー", 35, "[email protected]", true);
root.putPOJO("existing_user", existingUser);
// 動的にネストしたオブジェクトを作成
root.withObject("deep").withObject("nested").withObject("structure").put("value", 42);
// JSON Pointerを使用した作成
root.withObject("/settings/ui").put("theme", "dark");
root.withObject("/settings/notifications").put("email", true);
// 結果をJSON文字列として出力
String result = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(root);
System.out.println("作成されたJSON:\n" + result);
// 一部のフィールドの変更
root.put("age", 26); // 年齢を更新
root.get("contact").put("email", "[email protected]"); // メール更新
// 配列への要素追加
((ArrayNode) root.get("skills")).add("Jackson");
System.out.println("\n更新後のJSON:\n" +
mapper.writerWithDefaultPrettyPrinter().writeValueAsString(root));
}
// Tree ModelとPOJOの混合使用
public static void mixedApproach() throws IOException {
// 部分的に構造化されたJSONデータ
String mixedJson = """
{
"user": {
"name": "混合ユーザー",
"age": 28,
"email": "[email protected]"
},
"dynamic_metadata": {
"custom_field1": "value1",
"custom_field2": 123,
"nested": {
"some_flag": true
}
},
"known_settings": {
"theme": "light",
"language": "ja"
}
}
""";
JsonNode root = mapper.readTree(mixedJson);
// 既知の構造をPOJOに変換
User user = mapper.treeToValue(root.get("user"), User.class);
System.out.println("ユーザー: " + user.getName());
// 動的な部分をMapに変換
Map<String, Object> dynamicMetadata = mapper.treeToValue(
root.get("dynamic_metadata"), Map.class);
System.out.println("動的メタデータ: " + dynamicMetadata);
// 特定の値を直接取得
String theme = root.get("known_settings").get("theme").asText();
boolean someFlag = root.at("/dynamic_metadata/nested/some_flag").asBoolean();
System.out.println("テーマ: " + theme + ", フラグ: " + someFlag);
// 結果を新しいJSONとして組み立て
ObjectNode response = mapper.createObjectNode();
response.putPOJO("processed_user", user);
response.putPOJO("metadata_summary", Map.of(
"field_count", dynamicMetadata.size(),
"theme", theme,
"flag", someFlag
));
String responseJson = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(response);
System.out.println("処理結果:\n" + responseJson);
}
}
ストリーミングAPIによる高性能処理
import com.fasterxml.jackson.core.*;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
public class JacksonStreaming {
private static final ObjectMapper mapper = new ObjectMapper();
// 大容量JSONファイルのストリーミング書き込み
public static void streamingWrite() throws IOException {
File outputFile = new File("large_data.json");
try (JsonGenerator generator = mapper.createGenerator(outputFile, JsonEncoding.UTF8)) {
// 大容量配列の開始
generator.writeStartArray();
// 100万件のデータを段階的に書き込み
for (int i = 0; i < 1_000_000; i++) {
generator.writeStartObject();
generator.writeNumberField("id", i);
generator.writeStringField("name", "User " + i);
generator.writeStringField("email", "user" + i + "@example.com");
generator.writeBooleanField("active", i % 10 != 0);
generator.writeNumberField("score", i * 0.1);
// ネストしたオブジェクト
generator.writeObjectFieldStart("metadata");
generator.writeStringField("department", "Dept " + (i % 5));
generator.writeNumberField("level", i % 3 + 1);
generator.writeEndObject();
generator.writeEndObject();
// 進捗表示(10万件ごと)
if (i % 100_000 == 0) {
System.out.println("書き込み済み: " + i + " 件");
}
}
generator.writeEndArray();
}
System.out.println("ストリーミング書き込み完了");
}
// 大容量JSONファイルのストリーミング読み込み
public static void streamingRead() throws IOException {
File inputFile = new File("large_data.json");
if (!inputFile.exists()) {
System.out.println("入力ファイルが存在しません。先にstreamingWrite()を実行してください。");
return;
}
int count = 0;
int activeCount = 0;
double totalScore = 0.0;
try (JsonParser parser = mapper.createParser(inputFile)) {
// 配列の開始を確認
JsonToken token = parser.nextToken();
if (token != JsonToken.START_ARRAY) {
throw new IOException("配列形式のJSONを期待していましたが、異なる形式でした");
}
// 各オブジェクトを順次処理
while (parser.nextToken() == JsonToken.START_OBJECT) {
boolean active = false;
double score = 0.0;
// オブジェクト内のフィールドを順次処理
while (parser.nextToken() != JsonToken.END_OBJECT) {
String fieldName = parser.getCurrentName();
parser.nextToken(); // 値に移動
switch (fieldName) {
case "active":
active = parser.getBooleanValue();
break;
case "score":
score = parser.getDoubleValue();
break;
case "metadata":
// ネストしたオブジェクトをスキップ
parser.skipChildren();
break;
default:
// その他のフィールドはスキップ
parser.skipChildren();
break;
}
}
// 統計の更新
count++;
if (active) {
activeCount++;
}
totalScore += score;
// 進捗表示(10万件ごと)
if (count % 100_000 == 0) {
System.out.println("処理済み: " + count + " 件");
}
}
}
System.out.println("ストリーミング読み込み完了");
System.out.println("総件数: " + count);
System.out.println("アクティブユーザー: " + activeCount);
System.out.println("平均スコア: " + (totalScore / count));
}
// 選択的なデータ抽出
public static List<User> selectiveExtraction(File jsonFile, int minAge) throws IOException {
List<User> result = new ArrayList<>();
try (JsonParser parser = mapper.createParser(jsonFile)) {
if (parser.nextToken() != JsonToken.START_ARRAY) {
throw new IOException("配列形式のJSONを期待");
}
while (parser.nextToken() == JsonToken.START_OBJECT) {
String name = null;
int age = 0;
String email = null;
boolean active = false;
while (parser.nextToken() != JsonToken.END_OBJECT) {
String fieldName = parser.getCurrentName();
parser.nextToken();
switch (fieldName) {
case "name":
name = parser.getValueAsString();
break;
case "age":
age = parser.getIntValue();
break;
case "email":
email = parser.getValueAsString();
break;
case "active":
active = parser.getBooleanValue();
break;
default:
parser.skipChildren();
break;
}
}
// 条件に合致する場合のみリストに追加
if (age >= minAge && active) {
result.add(new User(name, age, email, active));
}
}
}
return result;
}
public static void demonstrateStreaming() throws IOException {
System.out.println("=== ストリーミングAPI デモ ===");
// 大容量データの書き込み
System.out.println("1. 大容量データ書き込み開始...");
long startTime = System.currentTimeMillis();
streamingWrite();
long writeTime = System.currentTimeMillis() - startTime;
System.out.println("書き込み時間: " + writeTime + "ms");
// 大容量データの読み込み
System.out.println("\n2. 大容量データ読み込み開始...");
startTime = System.currentTimeMillis();
streamingRead();
long readTime = System.currentTimeMillis() - startTime;
System.out.println("読み込み時間: " + readTime + "ms");
// ファイルサイズの確認
File file = new File("large_data.json");
System.out.println("ファイルサイズ: " + (file.length() / 1024 / 1024) + " MB");
}
}