Java で JSON を扱うには、 Jackson というライブラリが便利らしい。

Jackson は、POJO (Plain Old Java Object) であるシンプルなクラスを書いて、そこに JSON をマッピングするという機能が有名とか。

今回は、わざわざ新しく POJO なクラスを書くのではなく、 Java 標準ライブラリの java.util.Map と java.util.List にマッピングする方法を試してみた。

Jackson で JSON を Map や List に変換できると何が便利なのか

JSON を Java オブジェクトに変換するコードを用意しておくことで、ビジネスロジックからは Jackson の存在を意識しないでコードを書くことができる。

JSON から Java オブジェクトへのシンプルなマッピングのパターン

JSON データ型Java オブジェクト型
オブジェクト (キーと値)Map
配列List
文字列String
整数Integer
浮動小数点数Double
真偽値Boolean
nullnull

公式ドキュメント JacksonDataBinding - FasterXML Wiki には、変換できる Java オブジェクトについて書かれている。オブジェクトは LinkedHashMap、配列は ArrayList、整数は Integer / Long / BigInteger 、浮動小数点数は Double / BigDecimal へ変換できるらしい。

トップレベルがオブジェクトである JSON を Map に変換するサンプルコード


// JSON オブジェクトは Map<String, Object> にマッピングされる
private static Map<String, Object> readJsonObject(File json)
  throws JsonMappingException, JsonParseException, IOException {

  ObjectMapper mapper = new ObjectMapper();
  return mapper.readValue(json, new TypeReference<Map<String, Object>>(){});
}

トップレベルが配列である JSON を List に変換するサンプルコード


// JSON 配列は List<Object> にマッピングされる
private static List<Object> readJsonArray(File json)
  throws JsonMappingException, JsonParseException, IOException {

  ObjectMapper mapper = new ObjectMapper();
  return mapper.readValue(json, new TypeReference<List<Object>>(){});
}

Jackson のダウンロード

Jackson は複数の JAR ファイルで構成されていて、今回必要なのは、 Jackson Core, Jackson Annotations, Jackson databind の3つ。

Jackson の JAR ファイルは Central Maven repository からダウンロードできる。

For non-Maven use cases, you download jars from Central Maven repository or Wiki.

FasterXML/jackson-core · GitHub

今回ダウンロードするのは、安定版の最新バージョン 2.5.3 で、以下の3ファイル。

(って今みたら 2.5.4 がリリースされていた。。。)

JSON ファイルを読み込んで Java オブジェクトへ変換するサンプルコード

3種類のJSONファイルを読み込むサンプルコード。

import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Map;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class JacksonSample {

  public static void main(String[] args) throws Exception {
    readJsonObjectSample();
    readJsonArraySample();
    readJsonArrayStringSample();
  }

  // JSON オブジェクトは Map<String, Object> にマッピングされる
  private static Map<String, Object> readJsonObject(File json)
    throws JsonMappingException, JsonParseException, IOException {

    ObjectMapper mapper = new ObjectMapper();
    return mapper.readValue(json, new TypeReference<Map<String, Object>>(){});
  }

  // JSON 配列は List<Object> にマッピングされる
  private static List<Object> readJsonArray(File json)
    throws JsonMappingException, JsonParseException, IOException {

    ObjectMapper mapper = new ObjectMapper();
    return mapper.readValue(json, new TypeReference<List<Object>>(){});
  }

  private static void readJsonObjectSample()
    throws JsonMappingException, JsonParseException, IOException {
    
    System.out.println("***** readJsonObjectSample *****");
    File json = new File("object.json");
  
    // JSON オブジェクトは Map<String, Object> にマッピングされる
    Map<String, Object> map = readJsonObject(json);
    System.out.println("name: " + map.get("name"));
    System.out.println("age: " + map.get("age"));
    System.out.println("score: " + map.get("score"));
    System.out.println("alive: " + map.get("alive"));

    // null は null
    if(map.get("nulltest") == null){
      System.out.println("nulltest is null.");
    }else{
      System.out.println("nulltest is " + map.get("nulltest"));
    }
  
    // JSON 配列は List にマッピングされる
    List<String> friends = (List<String>) map.get("friends");
    for (String friend : friends) {
      System.out.println("friend: " + friend);
    }
    
    // 整数は Integer 型にマッピングされる
    List<Integer> integers = (List<Integer>) map.get("integers");
    for (Integer i : integers) {
      System.out.println("integer: " + i);
    }

    // 浮動小数点数は Double 型にマッピングされる
    List<Double> floats = (List<Double>) map.get("floats");
    for (Double f : floats) {
      System.out.println("float: " + f);
    }

    // 真偽値は Boolean 型にマッピングされる
    List<Boolean> booleans = (List<Boolean>) map.get("booleans");
    for (Boolean b : booleans) {
      System.out.println("boolean: " + b);
    }

    // オブジェクトは Map 型にマッピングされる
    Map<String, Object> favorites = (Map<String, Object>) map.get("favorites");
    for (String key : favorites.keySet()) {
      System.out.println("favorite: " + key + " = " + favorites.get(key));
    }
  }

  private static void readJsonArraySample()
    throws JsonMappingException, JsonParseException, IOException {

    System.out.println("***** readJsonArraySample *****");
    File json = new File("array.json");

    // JSON 配列は List
    List<Object> list = readJsonArray(json);
    for (Object obj : list) {
      // JSON オブジェクトは Map<String, Object> にマッピング
      Map<String, Object> x = (Map<String, Object>) obj;
      System.out.println("name: " + x.get("name"));
      System.out.println("age: " + x.get("age"));
    }
  }

  private static void readJsonArrayStringSample()
    throws JsonMappingException, JsonParseException, IOException {
    
    System.out.println("***** readJsonArrayStringSample *****");
    File json = new File("array_string.json");
    
    // JSON 配列は List<Object> にマッピング
    List<Object> list = readJsonArray(json);
    for (Object obj : list) {
      // 文字列の JSON 配列
      String name = (String)obj;
      System.out.println("name: " + name);
    }
  }
}
読み込むJSONファイル (object.json)

トップレベルがオブジェクト。


{
  "name" : "Alice",
  "age" : 10,
  "score" : 12.34,
  "alive" : true,
  "nulltest" : null,

  "friends" : [
    "Bob", "Carol"
  ],

  "integers" : [
    1, -2147483648, 2147483647
  ],

  "floats" : [
    1.5, -0.1
  ],

  "booleans" : [
    true, false
  ],

  "favorites" : {
    "book" : "Alice's Adventures in Wonderland",
    "food" : "candy"
  }
}
読み込むJSONファイル (array.json)

トップレベルが配列。


[
  {
    "name" : "Alice",
    "age": 10
  },
  {
    "name" : "Bob",
    "age": 20
  },
  {
    "name" : "Carol",
    "age": 30
  }
]
読み込むJSONファイル (array_string.json)

トップレベルが文字列の配列。


[ "Alice", "Bob", "Carol" ]
サンプルコードをコンパイル

今回の環境は、Mac OS X Yosemite + Java 1.8

警告が出るけど気にしない。


$ javac -classpath jackson-core-2.5.3.jar:jackson-annotations-2.5.3.jar:jackson-databind-2.5.3.jar JacksonSample.java
注意:JacksonSample.javaの操作は、未チェックまたは安全ではありません。
注意:詳細は、-Xlint:uncheckedオプションを指定して再コンパイルしてください。
実行結果

$ java -classpath ./:jackson-core-2.5.3.jar:jackson-annotations-2.5.3.jar:jackson-databind-2.5.3.jar JacksonSample
***** readJsonObjectSample *****
name: Alice
age: 10
score: 12.34
alive: true
nulltest is null.
friend: Bob
friend: Carol
integer: 1
integer: -2147483648
integer: 2147483647
float: 1.5
float: -0.1
boolean: true
boolean: false
favorite: book = Alice's Adventures in Wonderland
favorite: food = candy
***** readJsonArraySample *****
name: Alice
age: 10
name: Bob
age: 20
name: Carol
age: 30
***** readJsonArrayStringSample *****
name: Alice
name: Bob
name: Carol

Jackson で JSON を Map や List に変換できると便利だが欠点もある

便利だが、 POJO にマッピングしないということは、ひとつひとつデータを取り出すコードを自前で書く必要が出てくる。

JSON を Java で扱うのはなかなかめんどい。せめて XML の XPath のようなものが JSON にあればいいのにと思う。

参考資料

tags: java json jackson

Posted by NI-Lab. (@nilab)