動作確認環境
- Java 16
- Jackson 2.12.5
クラスでJSONの型を定義する
JacksonでJSONをJavaのオブジェクトに、あるいはその逆をするには、クラスでJSONの型を定義するのが一般的。
例えば{"level1Str":"v1","level1Obj":{"level2Str":"v2"}}
というJSONをJavaで読み書きしたければ、以下のようにクラスを定義する。
@NoArgsConstructor(access = AccessLevel.PRIVATE) // Jackson requires for deserialize
@AllArgsConstructor
@Getter
public class SampleJson {
private String level1Str;
private SampleJsonInner level1Obj;
@NoArgsConstructor(access = AccessLevel.PRIVATE) // Jackson requires for deserialize
@AllArgsConstructor
@Getter
public static class SampleJsonInner {
private String level2Str;
}
}
読み書きはそれぞれreadValue
とwriteValueAsString
を使用する。
static ObjectMapper objectMapper = new ObjectMapper();
public static void main(String[] args) {
useClass();
}
private static void useClass() {
try {
// from JSON
var jsonObj = objectMapper.readValue("{\"level1Str\":\"v1\",\"level1Obj\":{\"level2Str\":\"v2\"}}", SampleJson.class);
System.out.println(jsonObj.getLevel1Str()); // v1
System.out.println(jsonObj.getLevel1Obj().getLevel2Str()); // v2
// to JSON
String json = objectMapper.writeValueAsString(new SampleJson("v1", new SampleJsonInner("v2")));
System.out.println(json); // {"level1Str":"v1","level1Obj":{"level2Str":"v2"}}
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
APIのリクエストボディ/レスポンスボディなどはクラスに定義するべきである。特徴としては以下のようなものがあれば、クラスに定義したほうが静的型付けの恩恵を得られるはず。
- 自分が型を決められる
- 型が毎回変わらない
- インターフェースとして型を明確化したい
JSONをクラスにマッピングせずに読み書きするにはJsonNodeが便利
自分の提供するAPIではなく外部のAPIのレスポンスボディの極一部を利用したい場合や、ログを分析しやすくするためにJSON形式を使いたいだけでインターフェースとして型を明確にするメリットがほぼない場合は、JSONをクラスにマッピングしない方が楽に扱える。
読みはreadTree
を使う。書きはobjectNode
やarrayNode
等を使う。どちらもJsonNode
を取得できる。
private static void useJsonNode() {
// from JSON
try {
String otherApiResponseBody = "{\"level1Str\":\"v1\",\"level1Obj\":{\"level2Str\":\"v2\"}}";
JsonNode node = objectMapper.readTree(otherApiResponseBody);
// 本題ではないので深入りしないが、アクセス方法(path/get/at)と値の取得方法(~Value/as~)にはバラエティがあり、nullの扱い等が異なるので注意
// 詳しくは関連記事参照
System.out.println(node.path("level1Str").textValue()); // v1
System.out.println(node.get("level1Obj").get("level2Str").asText()); // v2
System.out.println(node.at("/level1Obj/level2Str").asText()); // v2
} catch (JsonProcessingException e) {
e.printStackTrace();
}
// to JSON
var factory = JsonNodeFactory.instance;
JsonNode node = factory.objectNode()
.put("level1Str", "v1")
.set("level1Obj", // 階層を掘りたい場合はputじゃなくてsetとobjectNodeを組み合わせる
factory.objectNode()
.put("level2Str", "v2")
.putPOJO("level2Obj", new Dto("v-x"))); // クラスが定義されていれば、これでも階層が掘れる
System.out.println(node.toString()); // {"level1Str":"v1","level1Obj":{"level2Str":"v2","level2Obj":{"x":"v-x"}}}
}
// to JSONで登場したDto定義
@AllArgsConstructor
@Getter
public class Dto {
private String x;
}
関連記事: JavaのJacksonでJSONの値を取得するメソッドのバリエーション(path|get|at), (textValue|asText)