JSONパースは「外の世界のデータを自分の型に落とす」技
JSON文字列化が「Javaのオブジェクトを外の世界に出す」技だとしたら、
JSONパースはその逆で、「外の世界から来た JSON を、自分の型に安全に落とし込む」技です。
ここを雑にやると、型が合わない・必須項目が抜けている・想定外の値が来て落ちる、など、
本番でしか起きないイヤなバグの温床になります。
だからこそ、「JSONパース用のユーティリティ」を一つ決めておくと、コード全体がかなり落ち着きます。
前提:文字列化ユーティリティと同じ ObjectMapper を使い回す
「JSON文字列化」と「JSONパース」は同じエンジンで動かす
JSON文字列化のときに使った Jackson の ObjectMapper は、パースでもそのまま使えます。
ここをバラバラにすると、「出すときと読むときでルールが違う」という最悪パターンになるので、
同じ設定の ObjectMapper を、文字列化とパースで共有するのが鉄則です。
例えば、前回の Jsons を少し拡張して、パースも扱えるようにします。
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
public final class Jsons {
private static final ObjectMapper MAPPER = createMapper();
private Jsons() {}
private static ObjectMapper createMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
return mapper;
}
public static String toJson(Object value) {
try {
return MAPPER.writeValueAsString(value);
} catch (JsonProcessingException e) {
throw new IllegalStateException("JSON 文字列化に失敗しました: " + value, e);
}
}
public static <T> T fromJson(String json, Class<T> type) {
try {
return MAPPER.readValue(json, type);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("JSON パースに失敗しました: " + json, e);
}
}
}
Javaここでの重要ポイントは、fromJson が 「JSON文字列+欲しい型」から、型付きのオブジェクトを返す窓口になっていることです。
呼び出し側は ObjectMapper を意識せず、「JSON をこの型にしてほしい」とだけ指定すればよくなります。
基本:JSON → POJO のパース
フィールド名が合っていれば、そのままマッピングされる
まずは、素直な POJO にパースする例からいきます。
public class User {
private String id;
private String name;
private int age;
public User() {}
public String getId() { return id; }
public String getName() { return name; }
public int getAge() { return age; }
public void setId(String id) { this.id = id; }
public void setName(String name) { this.name = name; }
public void setAge(int age) { this.age = age; }
}
Javaこの JSON をパースしてみます。
{"id":"u-001","name":"山田太郎","age":30}
コードはこうなります。
String json = """
{"id":"u-001","name":"山田太郎","age":30}
""";
User user = Jsons.fromJson(json, User.class);
System.out.println(user.getId()); // u-001
System.out.println(user.getName()); // 山田太郎
System.out.println(user.getAge()); // 30
Javaここで深掘りしたいのは、「JSON のキー名と、Java のフィールド(または getter/setter)の名前が一致していれば、自動でマッピングされる」ことです。
特別なアノテーションを付けなくても、基本的な POJO であればそのままパースできます。
逆に言うと、「JSON 側が user_id、Java 側が userId」のように名前が違う場合は、
アノテーション(@JsonProperty("user_id"))などで対応する必要があります。
例題:部分的な JSON を Map で受ける
「全部の型をきっちり決めなくてもいい」場面
外部サービスから返ってくる JSON が大きくて、
「とりあえず status と message だけ見たい」というような場面では、
POJO を定義せずに Map で受けるのもよくあるパターンです。
import java.util.Map;
String json = """
{"status":"ok","message":"done","extra":{"debug":true}}
""";
Map<?, ?> map = Jsons.fromJson(json, Map.class);
System.out.println(map.get("status")); // ok
System.out.println(map.get("message")); // done
System.out.println(map.get("extra")); // {debug=true} など
Javaここでのポイントは、「Map で受けると楽だが、型安全性は落ちる」ことです。map.get("status") の戻り値は Object なので、キャストミスやキー名のタイプミスに気づきにくくなります。
「一時的なデバッグ」「ログ用」「小さな JSON をざっくり見る」くらいなら Map でも十分ですが、
本番ロジックでガッツリ使うなら、素直に POJO を定義したほうが安全です。
例題:配列JSONを List にパースする
「ユーザー一覧」などの典型パターン
次は、配列になっている JSON をパースしてみます。
[
{"id":"u-001","name":"山田太郎","age":30},
{"id":"u-002","name":"佐藤花子","age":25}
]
これを List<User> にしたいのですが、Class<T> だけでは「List の中身の型」まで表現できません。
そこで Jackson の TypeReference を使います。
import com.fasterxml.jackson.core.type.TypeReference;
import java.util.List;
public final class Jsons {
// さっきの内容にメソッドを追加
public static <T> T fromJson(String json, TypeReference<T> typeRef) {
try {
return MAPPER.readValue(json, typeRef);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("JSON パースに失敗しました: " + json, e);
}
}
}
Java使う側はこうです。
String json = """
[
{"id":"u-001","name":"山田太郎","age":30},
{"id":"u-002","name":"佐藤花子","age":25}
]
""";
List<User> users = Jsons.fromJson(json, new TypeReference<List<User>>() {});
System.out.println(users.size()); // 2
System.out.println(users.get(0).getName()); // 山田太郎
Javaここでの重要ポイントは、「ジェネリクスを含む型(List<User> など)は、TypeReference で表現する」ことです。Class<List> だけだと「中身の型」が分からないので、Jackson が List<Map> のように解釈してしまいます。new TypeReference<List<User>>() {} という少し不思議な書き方は、
「List<User> という具体的な型情報を、ランタイムまで持ち込むためのおまじない」と覚えておくとよいです。
エラー処理とバリデーションの考え方
「パースに失敗したらどうするか」を最初に決める
JSONパースで必ず向き合うのが、「壊れた JSON が来たときどうするか」です。
fromJson の中では、JsonProcessingException を IllegalArgumentException にラップして投げ直しました。
これは、「JSON が想定どおりでないのは“入力の問題”なので、呼び出し側でちゃんと扱ってほしい」というメッセージでもあります。
呼び出し側では、例えばこんな方針を決めておきます。
try {
User user = Jsons.fromJson(requestBody, User.class);
// 正常処理
} catch (IllegalArgumentException e) {
// 400 Bad Request を返す、ログに JSON を出す、など
}
Javaここで大事なのは、「パースエラーを握りつぶさない」ことです。
「とりあえず null を返す」「空のオブジェクトを返す」といった実装にすると、
「どこで壊れた JSON が来ているのか」が分からなくなり、調査が地獄になります。
パース後の「業務バリデーション」とは分けて考える
JSONパースはあくまで「構造として正しいか」「型として合っているか」を見る段階です。
「このフィールドは必須」「この値は 0 以上」といった業務ルールは、
パース後に別のバリデーション層でチェックするほうが、責務の分離としてきれいです。
つまり、
- JSONパース:構造・型レベルのチェック
- 業務バリデーション:意味レベルのチェック
と分けて考えると、コードの見通しがよくなります。
まとめ:JSONパースユーティリティで身につけたい感覚
JSONパースは、「文字列を手で分解する」のではなく、
「ライブラリに任せつつ、“どの型に落とし込むか”をはっきり決める」ための技です。
押さえておきたい感覚は、まず「文字列化とパースで同じ ObjectMapper を共有し、設定を一箇所に集約する」こと。
次に、「POJO には Class<T>、List や Map などのジェネリクス型には TypeReference を使う」こと。
そして、「パースエラーは握りつぶさず、“入力が不正である”というシグナルとしてきちんと扱う」ことです。
