CodeGym /コース /JAVA 25 SELF /Jackson — JSONの読み書き、アノテーション

Jackson — JSONの読み書き、アノテーション

JAVA 25 SELF
レベル 46 , レッスン 1
使用可能

1. Jackson 入門

Java アプリケーションと外部とのデータ交換は、多くの場合次の問いに集約できます。「Java オブジェクトを JSON に — そしてその逆に — どう変換するか?」もちろん String.split や正規表現で自作パーサーを書くこともできます(そして苦しみを楽しむこともできます)が、実際のプロジェクトでは誰もそんなことはしません。

Java には JSON を扱うための人気ライブラリがいくつかあります。その中心が Jackson です。あまりに一般的なので Spring Boot に同梱されており、他の多くのフレームワークやライブラリでも使われています。

Jackson とは?

Jackson はシリアライズ(Java オブジェクトを JSON に変換)とデシリアライズ(その逆)を行う強力で柔軟なライブラリです。複数モジュールから成りますが、90% の用途では次の 2 つだけで足ります。

  • jackson-core — コア。低レベルのパーサー。
  • jackson-databind — 高レベルのモジュール。Java オブジェクトと JSON の相互変換を行います。

まず覚えるべきことは 1 つ。ObjectMapper というクラスを見かけたら、それは Jackson です。

Jackson が JSON 処理のデファクトスタンダードと見なされるのは、簡潔さと強力さを両立しているからです。シリアライズ/デシリアライズは数行のコードで済み、初心者にも扱いやすい一方で、アノテーションや豊富な設定により、コレクション、ネストしたエンティティ、さまざまな形式の日時など、変換の挙動を細かく制御できます。

さらに Jackson は非常に高速かつ省メモリで、データ量の多い実案件では重要な特性です。開発チームは新しい Java バージョンや JSON 標準の更新を追随しており、シンプルなアプリから大規模なエンタープライズシステムまで、信頼できる選択肢であり続けます。

2. JSON の読み込み(デシリアライズ)

まずは JSON 文字列を読み取り、Java オブジェクトに変換してみましょう。そのために必要なのは次のとおりです。

  • データクラス(例: Person
  • Jackson の ObjectMapper クラス

Jackson の導入

もし Maven を使っているなら、pom.xml に次を追加します。

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.17.0</version>
</dependency>

Gradle の場合も同様です。

implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.0'

クラスの例

public class Person {
    public String name;
    public int age;
}

注意: 簡単にするため、フィールドは public にしています。後ほど private フィールドとゲッター/セッターの扱いについて説明します。

JSON の例

{
  "name": "Alice",
  "age": 30
}

デシリアライズ: JSON をオブジェクトに変換

import com.fasterxml.jackson.databind.ObjectMapper;

public class Main {
    public static void main(String[] args) throws Exception {
        String json = "{\"name\": \"Alice\", \"age\": 30}";

        ObjectMapper mapper = new ObjectMapper();
        Person person = mapper.readValue(json, Person.class);

        System.out.println(person.name); // Alice
        System.out.println(person.age);  // 30
    }
}

ここでは Jackson が JSON 文字列をパースし、クラスのフィールドやゲッターに名前が一致するプロパティを見つけ、readValue を使って対応する値でオブジェクトを埋めます。

オブジェクト配列(リスト)のデシリアライズ

配列があるとします。

[
  { "name": "Bob", "age": 22 },
  { "name": "Eve", "age": 27 }
]

これを List にデシリアライズします。

import com.fasterxml.jackson.core.type.TypeReference;
// ...

String json = "[{\"name\": \"Bob\", \"age\": 22}, {\"name\": \"Eve\", \"age\": 27}]";
ObjectMapper mapper = new ObjectMapper();

List<Person> people = mapper.readValue(json, new TypeReference<List<Person>>() {});

for (Person p : people) {
    System.out.println(p.name + " (" + p.age + ")");
}

「なぜ mapper.readValue(json, List.class) と書けないの?」と思うかもしれません。Java のジェネリクスはコンパイル時に型消去されます。そのため Jackson にリストの要素型が Person であると伝えるため、TypeReference が必要なのです。

3. JSON の書き出し(シリアライズ)

次は逆方向、Java オブジェクトを JSON 文字列に変換してみましょう。

ObjectMapper mapper = new ObjectMapper();

Person person = new Person();
person.name = "Charlie";
person.age = 40;

String json = mapper.writeValueAsString(person);
System.out.println(json);
// {"name":"Charlie","age":40}

オブジェクトのリストをシリアライズ

ObjectMapper mapper = new ObjectMapper();

List<Person> people = new ArrayList<>();
people.add(new Person("Anna", 25));
people.add(new Person("Dmitry", 31));

String json = mapper.writeValueAsString(people);
System.out.println(json);
// [{"name":"Anna","age":25},{"name":"Dmitry","age":31}]

ファイルへ書き出し

ObjectMapper mapper = new ObjectMapper();

Person person = new Person();
person.name = "Charlie";
person.age = 40;

mapper.writeValue(new File("person.json"), person);
// person.json ファイルには JSON オブジェクトが含まれます

見やすい(プリティ)JSON

既定では Jackson はすべてを 1 行で出力します。より「人が読みやすい」書式 — pretty printing — も用意されています。これは、インデントや改行を付けて整ったフォーマットで JSON を出力する機能です。

コンパクトさを優先する「通常」の JSON と違い、「見やすい」形式は人間のためのものです。ログやファイル、画面上でデータ構造を確認しやすくなります。

ObjectMapper mapper = new ObjectMapper();

Person person = new Person();
person.name = "Charlie";
person.age = 40;

String prettyJson = mapper.writerWithDefaultPrettyPrinter()
                          .writeValueAsString(person);

System.out.println(prettyJson);
/*
{
  "name" : "Charlie",
  "age" : 40
}
*/

4. Jackson のアノテーション

Jackson にはシリアライズ/デシリアライズの挙動を制御できる多数のアノテーションがあります。代表的なものを挙げます。

@JsonProperty

クラスのフィールド名と異なる JSON 側のプロパティ名を指定できます。

import com.fasterxml.jackson.annotation.JsonProperty;

public class Person {
    @JsonProperty("full_name")
    public String name;

    public int age;
}
{"full_name": "Olga", "age": 28}

Jackson は、JSON の full_name をオブジェクトの name フィールドに対応付けます。

@JsonIgnore

特定のフィールドをシリアライズ/デシリアライズ対象から外したい場合:

import com.fasterxml.jackson.annotation.JsonIgnore;

public class Person {
    public String name;

    @JsonIgnore
    public int age;
}

オブジェクトにあっても、JSON には age フィールドは出力されません。

@JsonInclude

どのフィールドを JSON に含めるかを制御します。例: 非 null のフィールドだけを出力する。

import com.fasterxml.jackson.annotation.JsonInclude;

@JsonInclude(JsonInclude.Include.NON_NULL)
public class Person {
    public String name;
    public Integer age;
}

もし age == null の場合、JSON に "age" キーは現れません。

@JsonFormat

日時のシリアライズ形式を指定できます。

import com.fasterxml.jackson.annotation.JsonFormat;
import java.util.Date;

public class Event {
    public String title;

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")
    public Date date;
}
Event event = new Event();
event.title = "Hackathon";
event.date = new Date();

ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(event);
// {"title":"Hackathon","date":"2024-06-07 15:23:00"}

例: まとめて適用

import com.fasterxml.jackson.annotation.*;

@JsonInclude(JsonInclude.Include.NON_NULL)
public class Person {
    @JsonProperty("full_name")
    private String name;

    private int age;

    @JsonIgnore
    private String password;

    // private フィールドには 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; }
}

5. 実践: アノテーションを踏まえたシリアライズ/デシリアライズ

学習用アプリを拡張してみましょう。いまやクラス User は private フィールド、登録日、そして JSON に含めたくないパスワードを持ちます。

import com.fasterxml.jackson.annotation.*;
import java.util.Date;

@JsonInclude(JsonInclude.Include.NON_NULL)
public class User {
    @JsonProperty("login")
    private String username;

    private int age;

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
    private Date registered;

    @JsonIgnore
    private String password;

    // getter/setter は必須!
    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }

    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }

    public Date getRegistered() { return registered; }
    public void setRegistered(Date registered) { this.registered = registered; }

    public String getPassword() { return password; }
    public void setPassword(String password) { this.password = password; }
}

シリアライズ

import com.fasterxml.jackson.databind.ObjectMapper;
import java.text.SimpleDateFormat;

ObjectMapper mapper = new ObjectMapper();

User user = new User();
user.setUsername("superuser");
user.setAge(42);
user.setRegistered(new SimpleDateFormat("yyyy-MM-dd").parse("2024-06-07"));
user.setPassword("qwerty123"); // JSON には含まれません!

String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(user);

System.out.println(json);
/*
{
  "login" : "superuser",
  "age" : 42,
  "registered" : "2024-06-07"
}
*/

デシリアライズ

import com.fasterxml.jackson.databind.ObjectMapper;

ObjectMapper mapper = new ObjectMapper();

String json = "{ \"login\": \"superuser\", \"age\": 42, \"registered\": \"2024-06-07\" }";
User user = mapper.readValue(json, User.class);

System.out.println(user.getUsername()); // superuser
System.out.println(user.getAge());      // 42
System.out.println(user.getRegistered());// Fri Jun 07 00:00:00 ...
System.out.println(user.getPassword()); // null(これで良い!)

6. Jackson でよくあるミス

エラー №1: 引数なしコンストラクタがない。
引数なしコンストラクタがないと Jackson はクラスのインスタンスを生成できません。引数ありコンストラクタだけを定義しているクラスでよく起こります。

エラー №2: private フィールドに getter/setter がない。
すべてのフィールドを private にしたのに getter/setter を用意し忘れると、(デフォルト設定では)Jackson はデシリアライズ時に値を設定できません。

エラー №3: フィールド名の不一致。
JSON のプロパティ名が Java 側のフィールド/ゲッター名と違うと対応付けられません。@JsonProperty を使いましょう。

エラー №4: 日付フォーマットが違う。
JSON の日付フォーマットが Java 側の期待と合わないと、Jackson はパースエラーを投げます。設定には @JsonFormat を使いましょう。

エラー №5: @JsonIgnore が付いたフィールドをシリアライズしようとする。
そのようなフィールドは JSON に出力されません — 仕様です。

エラー №6: 型指定なしでコレクションをシリアライズ/デシリアライズする。
TypeReference を使わないと、Jackson はコレクション要素の型を理解できません。

エラー №7: ファイルの読み書き時の例外。
ファイル操作では IOException のハンドリングを忘れないでください。

1
タスク
JAVA 25 SELF, レベル 46, レッスン 1
ロック未解除
ユーザーのデータの機密性 🕵️‍♀️
ユーザーのデータの機密性 🕵️‍♀️
1
タスク
JAVA 25 SELF, レベル 46, レッスン 1
ロック未解除
大学のカレンダーでのイベント管理 🗓️
大学のカレンダーでのイベント管理 🗓️
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION