JSON文字列化は「データを言語の壁から解放する」技
業務システムでは、フロントエンドとの通信、外部API連携、ログ出力、設定ファイルなど、あらゆるところで JSON が使われます。
Java のオブジェクトを JSON 文字列に変換することを「シリアライズ(文字列化)」と言いますが、
ここを雑にやると「フィールドが抜けている」「日付の形式がバラバラ」「null の扱いが揺れる」といった事故が起きやすくなります。
だからこそ、「JSON文字列化」をユーティリティとしてきちんと固めておくと、
どのクラスでも、どの機能でも、「同じルールで」「同じフォーマットで」JSON を出せるようになります。
基本方針:自前で組み立てない、ライブラリに任せる
文字列連結で JSON を作るのは即やめる
まず大前提として、次のようなコードは封印してほしいです。
// 絶対にやめたほうがいい例
String json = "{ \"id\": \"" + id + "\", \"name\": \"" + name + "\" }";
Java一見動きそうですが、name にダブルクォートや改行が入った瞬間に壊れますし、
数値や boolean、null の扱いもすぐに破綻します。
JSON は「構造化データ」なので、文字列連結ではなく「オブジェクト → JSON」をライブラリに任せるのが鉄則です。
Java では Jackson や Gson が定番ですが、ここでは Jackson を例にします。
Jackson を使った最小の JSON文字列化ユーティリティ
ObjectMapper を一箇所に集約する
Jackson の中心になるのが ObjectMapper です。
これを毎回 new するのではなく、「アプリ全体で共有する設定済みインスタンス」としてユーティリティに閉じ込めます。
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 String toPrettyJson(Object value) {
try {
return MAPPER.writerWithDefaultPrettyPrinter()
.writeValueAsString(value);
} catch (JsonProcessingException e) {
throw new IllegalStateException("JSON 整形出力に失敗しました: " + value, e);
}
}
}
Javaここで深掘りしたい重要ポイントは二つあります。
一つ目は、「ObjectMapper を static に一つだけ持っている」ことです。
ObjectMapper はスレッドセーフなので、アプリ全体で共有して構いません。
毎回 new するとパフォーマンスも悪くなるし、設定もバラバラになってしまいます。
二つ目は、「例外をラップして RuntimeException にしている」ことです。writeValueAsString はチェック例外を投げますが、
「ログ出力用に JSON にしたい」「デバッグ用に JSON にしたい」といった場面では、
呼び出し側で毎回 try-catch するのはつらいので、ユーティリティ側で包んでしまう設計がよく使われます。
例題:シンプルな POJO を JSON にする
フィールド名がそのままキーになる
まずは、素直な POJO を JSON にしてみます。
public class User {
private String id;
private String name;
private int age;
public User() {}
public User(String id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
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 にします。
User user = new User("u-001", "山田太郎", 30);
String json = Jsons.toJson(user);
System.out.println(json);
Java出力イメージはこうです。
{"id":"u-001","name":"山田太郎","age":30}
Javaここでのポイントは、「Java のフィールド名/getter 名が、そのまま JSON のキーになる」ことです。
特別なアノテーションを付けなくても、基本的な POJO であれば自動的に JSON に変換されます。
また、toPrettyJson を使えば、整形された見やすい JSON になります。
System.out.println(Jsons.toPrettyJson(user));
Java{
"id" : "u-001",
"name" : "山田太郎",
"age" : 30
}
整形版はログやデバッグには便利ですが、通信やファイル保存では無駄な空白が増えるので、
「本番用は toJson」「人間が見る用は toPrettyJson」と使い分けるのが実務的です。
Map や List をそのまま JSON にする
「とりあえず構造だけ作って返したい」場面
POJO を定義するほどでもない簡単なレスポンスやログでは、Map や List をそのまま JSON にすることもよくあります。
import java.util.Map;
Map<String, Object> payload = Map.of(
"status", "ok",
"count", 3,
"items", java.util.List.of("A", "B", "C")
);
String json = Jsons.toJson(payload);
System.out.println(json);
Java出力イメージはこうです。
{"status":"ok","count":3,"items":["A","B","C"]}
ここでの重要ポイントは、「Jsons.toJson は Object なら何でも受け取れる」ことです。
POJO だけでなく、Map、List、ネストした構造など、
「Java のオブジェクトグラフ」をそのまま JSON に変換してくれます。
ただし、あまり複雑な Map のネストを多用すると、型安全性が下がるので、
「ちゃんとした API レスポンス」などは素直に POJO を定義したほうが読みやすくなります。
例題:ログに「オブジェクトの状態」を JSON で出す
toString の代わりに JSON を使う
業務システムのログで、オブジェクトの状態を出したいとき、toString を自前で実装する代わりに「JSON 文字列化」を使うと、とても読みやすくなります。
public class Order {
private String id;
private String userId;
private int amount;
// コンストラクタや getter/setter は省略
@Override
public String toString() {
return Jsons.toJson(this);
}
}
Javaこうしておくと、ログ出力はこう書けます。
Order order = ...;
System.out.println("created order: " + order);
Javaログには、例えばこんな感じで出ます。
created order: {"id":"o-001","userId":"u-001","amount":5000}
ここで深掘りしたいのは、「toString を JSON にしておくと、“人間にも機械にも優しいログ”になる」ことです。
人間は JSON を見て直感的に内容を理解できますし、
後からログを機械処理したくなったときも、そのまま JSON としてパースできます。
ただし、toString で巨大なオブジェクトを丸ごと JSON にするとログが膨れ上がるので、
「どこまで出すか」はクラスごとに考える必要があります。
JSON文字列化で意識したい「設計のポイント」
設定を一箇所に集約する
日付のフォーマット、null の扱い、フィールド名の命名規則(camelCase か snake_case か)など、
JSON の細かいルールは、ObjectMapper の設定で変えられます。
これを各所でバラバラに設定すると、「この API は yyyy-MM-dd、あっちはタイムスタンプ」といったカオスになります。
ユーティリティの createMapper の中で、「プロジェクトとしての標準」を一箇所にまとめておくと、
どこで JSON を出しても同じルールが適用されるようになります。
例えば、snake_case にしたいならこんな感じです。
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
mapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
Javaこれで、userId というフィールドは JSON 上では user_id になります。
「例外をどう扱うか」を決めておく
toJson で例外が出るのは、たいてい「シリアライズできない型を持っている」「循環参照している」など、
設計上の問題であることが多いです。
そのため、「例外が出たら即気づきたい」場面では、今回のように RuntimeException にラップして投げ直すのが向いています。
一方、「ログ出力用なので、失敗したらプレーンな toString にフォールバックしたい」といった場面もあります。
そういうときは、もう一段ラッパーを用意してもよいです。
public static String safeToJson(Object value) {
try {
return toJson(value);
} catch (RuntimeException e) {
return String.valueOf(value);
}
}
Java「どこで厳しく落とすか」「どこで緩くフォールバックするか」を決めておくと、運用で迷いません。
まとめ:JSON文字列化ユーティリティで身につけたい感覚
JSON文字列化は、「文字列連結でそれっぽく作る」のではなく、
「ライブラリに任せつつ、プロジェクトとしてのルールを一箇所に集約する」ための技です。
押さえておきたい感覚は、まず「ObjectMapper を static に一つだけ持ち、設定を集中管理する」こと。
次に、「toJson/toPrettyJson のようなユーティリティメソッドを用意して、どこからでも同じ書き方で JSON を出せるようにする」こと。
そして、「POJO、Map、List など、Java のオブジェクトをそのまま JSON にできる」という感覚を持ち、
ログやデバッグ、API レスポンスなどに積極的に活用していくことです。
