Jackson使い方メモ

  • 409
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

Jackson とは?

Java 用の JSON パーサーライブラリの1つ。

Java オブジェクトと JSON の相互変換とかができる。

Jersey などで利用されてて、よく見かける気がする。
デファクトスタンダード?

インストール

build.gradle
dependencies {
    compile 'com.fasterxml.jackson.core:jackson-databind:2.3.4'
}

Hello World

Hoge.java
package sample.jackson;

public class Hoge {
    public int id;
    public String name;

    @Override
    public String toString() {
        return "Hoge [id=" + id + ", name=" + name + "]";
    }
}

Javaオブジェクト→JSON文字列

package sample.jackson;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class Main {

    public static void main(String[] args) throws JsonProcessingException {
        Hoge hoge = new Hoge();
        hoge.id = 10;
        hoge.name = "hoge";

        ObjectMapper mapper = new ObjectMapper();
        String json = mapper.writeValueAsString(hoge);

        System.out.println(json);
    }
}
実行結果
{"id":10,"name":"hoge"}

JSON文字列→Javaオブジェクト

package sample.jackson;

import java.io.IOException;

import com.fasterxml.jackson.databind.ObjectMapper;

public class Main {

    public static void main(String[] args) throws IOException {
        String json = "{\"id\":20, \"name\":\"HOGE\"}";

        ObjectMapper mapper = new ObjectMapper();
        Hoge hoge = mapper.readValue(json, Hoge.class);

        System.out.println(hoge);
    }
}
実行結果
Hoge [id=20, name=HOGE]

説明

ObjectMapper クラスを通じて、 Java オブジェクトと JSON 文字列の相互変換ができる。

String 以外にも File や OutputStream 、 URL などをインプット・アウトプットに指定できるメソッドが用意されているので、柔軟な入出力が可能。

フィールドの可視性が public な場合は、 Getter/Setter は無くても変換できる。
フィールドの可視性が protected 以下の場合は、 Getter/Setter が必要。

入出力の設定

package sample.jackson;

import java.io.IOException;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;

public class Main {

    public static void main(String[] args) throws IOException {
        Hoge hoge = new Hoge();
        hoge.id = 10;
        hoge.name = "Hoge";

        ObjectMapper mapper = new ObjectMapper();
        mapper.enable(SerializationFeature.INDENT_OUTPUT);

        String json = mapper.writeValueAsString(hoge);

        System.out.println(json);
    }
}
実行結果
{
  "id" : 10,
  "name" : "Hoge"
}

enable() メソッドと disable() メソッドを使って、解析の方法などを細かく設定できる。

上記例の場合、 SerializationFeature.INDENT_OUTPUT を有効にすることで、出力される JSON 文字列をインデントして見やすくしている。

シリアライズ(Java → JSON)の設定は SerializationFeature、デシリアライズ(JSON → Java)の設定は DeserializationFeature で指定する。

型引数を持つクラスをデシリアライズする

package sample.jackson;

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

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

public class Main {

    public static void main(String[] args) throws IOException {
        String json = "[{\"id\":15, \"name\":\"hoge\"}, {\"id\":16, \"name\":\"fuga\"}]";

        ObjectMapper mapper = new ObjectMapper();

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

        System.out.println(list);
    }
}
実行結果
[Hoge [id=15, name=hoge], Hoge [id=16, name=fuga]]

型引数を持つクラス(List や Map)をデシリアライズする場合は、 TypeReference を使用する。

TypeReference の型引数にデシリアライズ後の型を渡すことで、型安全なデシリアライズができる。

アノテーションで調整する

基本

  • フィールドに設定するアノテーションは、基本 Getter/Setter に設定することでも同じ効果が得られる。
  • Getter/Setter のいずれかをアノテートすれば、もう一方のメソッドをアノテートする必要はない。

プロパティの名前を変更する

Hoge.java
package sample.jackson;

import com.fasterxml.jackson.annotation.JsonProperty;

public class Hoge {
    public int id;

    @JsonProperty("aaa")
    public String name;
}
package sample.jackson;

import java.io.IOException;

import com.fasterxml.jackson.databind.ObjectMapper;

public class Main {

    public static void main(String[] args) throws IOException {
        Hoge hoge = new Hoge();
        hoge.id = 10;
        hoge.name = "Hoge";

        ObjectMapper mapper = new ObjectMapper();

        String json = mapper.writeValueAsString(hoge);

        System.out.println(json);
    }
}
実行結果
{"id":10,"aaa":"Hoge"}

@JsonProperty でプロパティの名前を変更できる。

デフォルトは、 Java のフィールド名が使用される。

任意のフィールドを変換の対象外にする

個々のフィールドで指定する

Hoge.java
package sample.jackson;

import com.fasterxml.jackson.annotation.JsonIgnore;

public class Hoge {
    public int id;

    @JsonIgnore
    public String name;
}
実行結果
{"id":10}

@JsonIgnore を設定すると、そのフィールドが変換の対象外になる。

まとめて設定する

Hoge.java
package sample.jackson;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties({"id", "age"})
public class Hoge {
    public int id;
    public String name;
    public int age;
}
package sample.jackson;

import java.io.IOException;

import com.fasterxml.jackson.databind.ObjectMapper;

public class Main {

    public static void main(String[] args) throws IOException {
        Hoge hoge = new Hoge();
        hoge.id = 10;
        hoge.name = "Hoge";
        hoge.age = 25;

        ObjectMapper mapper = new ObjectMapper();

        String json = mapper.writeValueAsString(hoge);

        System.out.println(json);
    }
}
実行結果
{"name":"Hoge"}

@JsonIgnoreProperties をクラスに設定することで、複数のフィールドをまとめて設定できる。

デシリアライズ時に存在しないプロパティがある場合のエラーを回避する

JSON の中に、デシリアライズ後の Java オブジェクトに存在しないプロパティがある場合、実行時にエラーが発生する。

Hoge.java
package sample.jackson;


public class Hoge {
    public int id;
    public String name;

    @Override
    public String toString() {
        return "Hoge [id=" + id + ", name=" + name + "]";
    }
}
package sample.jackson;

import java.io.IOException;

import com.fasterxml.jackson.databind.ObjectMapper;

public class Main {

    public static void main(String[] args) throws IOException {
        String json = "{\"id\":10, \"name\":\"hoge\", \"age\":30}";

        ObjectMapper mapper = new ObjectMapper();

        Hoge hoge = mapper.readValue(json, Hoge.class);

        System.out.println(hoge);
    }
}
実行結果
Exception in thread "main" com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "age" (class sample.jackson.Hoge), not marked as ignorable (2 known properties: "id", "name"])
 at [Source: java.io.StringReader@12843fce; line: 1, column: 34] (through reference chain: sample.jackson.Hoge["age"])
(以下略)

これを回避するためには、 @JsonIgnoreProperties でクラスをアノテートして、 ignoreUnknown 属性に true を設定する。

Hoge.java
package sample.jackson;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown=true)
public class Hoge {
    public int id;
    public String name;

    @Override
    public String toString() {
        return "Hoge [id=" + id + ", name=" + name + "]";
    }
}
実行結果
Hoge [id=10, name=hoge]

デシリアライズ時のコンストラクタ(ファクトリメソッド)を指定する

Hoge.java
package sample.jackson;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

public class Hoge {
    private final int id;
    private final String name;

    @JsonCreator
    private Hoge(@JsonProperty("id") int id, @JsonProperty("name") String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public String toString() {
        return "Hoge [id=" + id + ", name=" + name + "]";
    }
}
package sample.jackson;

import java.io.IOException;

import com.fasterxml.jackson.databind.ObjectMapper;

public class Main {

    public static void main(String[] args) throws IOException {
        String json = "{\"id\":15, \"name\":\"hoge\"}";

        ObjectMapper mapper = new ObjectMapper();

        Hoge hoge = mapper.readValue(json, Hoge.class);

        System.out.println(hoge);
    }
}
実行結果
Hoge [id=15, name=hoge]

デフォルトでは、デシリアライズ時にデフォルトコンストラクタが使用される(デフォルトコンストラクタがない場合は例外がスローされる)。

何らかの理由でデフォルトコンストラクタでデシリアライズしてほしくない場合は、 @JsonCreator を使ってデシリアライズ時のコンストラクタを指定できる。

コンストラクタは private でも構わない。

また、コンストラクタだけでなく、以下のようにファクトリメソッドに @JsonCreator をアノテートすることもできる。

Hoge.java
package sample.jackson;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;

public class Hoge {
    private final int id;
    private final String name;

    @JsonCreator
    public static Hoge create(@JsonProperty("id") int id, @JsonProperty("name") String name) {
        return new Hoge(id, name);
    }

    private Hoge(int id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public String toString() {
        return "Hoge [id=" + id + ", name=" + name + "]";
    }
}

型情報を JSON に出力する

Hoge.java
package sample.jackson;

import com.fasterxml.jackson.annotation.JsonTypeInfo;

@JsonTypeInfo(use=JsonTypeInfo.Id.CLASS)
public class Hoge {
    public int id;
    public String name;
}
出力結果
{"@class":"sample.jackson.Hoge","id":10,"name":"hoge"}

@JsonTypeInfo でクラスをアノテートすることで、そのクラスの情報を JSON 上に出力することができる。
この情報は、デシリアライズするときに必要になることがある。

各属性で設定できる情報

use 属性

use 属性では、型情報をどのような形式で出力するかを指定する。

指定できる値は、 JsonTypeInfo.Id に定義されている値から選択する。

出力例 説明
CLASS {"@class":"sample.jackson.Hoge","id":10,"name":"hoge"} クラスの FQCN を出力する。
CUSTOM - 出力方法を独自で実装する。参考
MINIMAL_CLASS {"@c":".Hoge","id":10,"name":"hoge"} 可能な限りクラス名を省略した形で出力する。
NAME {"@type":"Hoge","id":10,"name":"hoge"} クラスの単純名を出力する。
NONE {"id":10,"name":"hoge"} 型情報を出力しない。

include 属性

include 属性では、どのような形式で型情報を出力かを指定する。

指定できる値は、 JsonTypeInfo.As に定義されている値から選択する。

出力例 説明
PROPERTY {"@class":"sample.jackson.Hoge","id":10,"name":"hoge"} 型情報用のプロパティが追加で出力される。
WRAPPER_ARRAY ["sample.jackson.Hoge",{"id":10,"name":"hoge"}] 全体が配列で出力され、一番目の要素に型情報、二番目の要素にシリアライズされたオブジェクトが設定される。
WRAPPER_OBJECT {"sample.jackson.Hoge":{"id":10,"name":"hoge"}} 全体がオブジェクトで出力され、型情報がキー、シリアライズされたオブジェクトが値になる。
EXTERNAL_PROPERTY - 後述
EXISTING_PROPERTY - よくわからなかった。
EXTERNAL_PROPERTY
Dto
package sample.jackson;

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.As;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;

public class Dto {
    @JsonTypeInfo(use=Id.CLASS, include=As.EXTERNAL_PROPERTY)
    public Hoge hoge;
}
package sample.jackson;

import java.io.IOException;

import com.fasterxml.jackson.databind.ObjectMapper;

public class Main {

    public static void main(String[] args) throws IOException {
        Hoge hoge = new Hoge();
        hoge.id = 10;
        hoge.name = "hoge";

        Dto dto = new Dto();
        dto.hoge = hoge;

        ObjectMapper mapper = new ObjectMapper();

        String json = mapper.writeValueAsString(dto);

        System.out.println(json);
    }
}
実行結果
{"hoge":{"id":10,"name":"hoge"},"@class":"sample.jackson.Hoge"}

EXTERNAL_PROPERTY を指定する場合、 @JsonTypeInfo はクラスではなくプロパティにアノテートする。

型情報は、そのプロパティの兄弟プロパティに出力される。

property 属性

Hoge.java
package sample.jackson;

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;

@JsonTypeInfo(use=Id.CLASS, property="typeName")
public class Hoge {
    public int id;
    public String name;

    @Override
    public String toString() {
        return "Hoge [id=" + id + ", name=" + name + "]";
    }
}
実行結果
{"typeName":"sample.jackson.Hoge","id":10,"name":"hoge"}

型情報のプロパティ名を指定できる。
includeWRAPPER_ARRAY または WRAPPER_OBJECT の場合は無視される。

型情報が

ないとき~

AbstractClass.java
package sample.jackson;

public abstract class AbstractClass {
    public int id;
    public String name;
}
Hoge.java
package sample.jackson;

public class Hoge extends AbstractClass {
    @Override
    public String toString() {
        return "Hoge [id=" + id + ", name=" + name + "]";
    }
}
Fuga.java
package sample.jackson;

public class Fuga extends AbstractClass {
    @Override
    public String toString() {
        return "Fuga [id=" + id + ", name=" + name + "]";
    }
}
package sample.jackson;

import java.io.IOException;

import com.fasterxml.jackson.databind.ObjectMapper;

public class Main {

    public static void main(String[] args) throws IOException {
        Hoge hoge = new Hoge();
        hoge.id = 10;
        hoge.name = "hoge";

        Fuga fuga = new Fuga();
        fuga.id = 20;
        fuga.name = "fuga";

        Dto dto = new Dto();
        dto.list.add(hoge);
        dto.list.add(fuga);

        ObjectMapper mapper = new ObjectMapper();

        String json = mapper.writeValueAsString(dto);

        System.out.println(json);

        dto = mapper.readValue(json, Dto.class);

        System.out.println(dto);
    }
}
実行結果
{"list":[{"id":10,"name":"hoge"},{"id":20,"name":"fuga"}]}
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of sample.jackson.AbstractClass, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information
 at [Source: java.io.StringReader@2ef5e5e3; line: 1, column: 10] (through reference chain: sample.jackson.Dto["list"])
    at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:164)
(以下略)

AbstractClass のインスタンスを生成しようとして、エラーになってしまう。

あるとき~

AbstractClass
package sample.jackson;

import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;

@JsonTypeInfo(use=Id.CLASS)
public abstract class AbstractClass {
    public int id;
    public String name;
}
実行結果
{"list":[{"@class":"sample.jackson.Hoge","id":10,"name":"hoge"},{"@class":"sample.jackson.Fuga","id":20,"name":"fuga"}]}
Dto [list=[Hoge [id=10, name=hoge], Fuga [id=20, name=fuga]]]

ちゃんと具象クラスのインスタンスが生成されている。

型情報の名前を任意の値に変更する

AbstractClass.java
package sample.jackson;

import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
import com.fasterxml.jackson.annotation.JsonTypeInfo.Id;

@JsonTypeInfo(use=Id.NAME)
@JsonSubTypes({
    @Type(name="HogeClass", value=Hoge.class),
    @Type(name="HogeClass", value=Fuga.class)
})
public abstract class AbstractClass {
    public int id;
    public String name;
}
実行結果
{"list":[{"@type":"HogeClass","id":10,"name":"hoge"},{"@type":"FugaClass","id":20,"name":"fuga"}]}

@JsonSubTypes アノテーションで、型ごとに JSON に出力するときの値を指定できる。

その場合、 @JsonTypeInfouse 属性は、 NAME にしておく必要がある。

各具象クラスごとに名前を設定する

しかし、この方法だと抽象クラスに具象クラスの情報が載ってしまい、行儀が悪い。
そんなときは、 @JsonTypeName を使えば、各具象クラスごとに名前を設定できる。

Hoge.java
package sample.jackson;

import com.fasterxml.jackson.annotation.JsonTypeName;

@JsonTypeName("HogeHoge")
public class Hoge extends AbstractClass {
    @Override
    public String toString() {
        return "Hoge [id=" + id + ", name=" + name + "]";
    }
}
Fuga.java
package sample.jackson;

import com.fasterxml.jackson.annotation.JsonTypeName;

@JsonTypeName("FugaFuga")
public class Fuga extends AbstractClass {
    @Override
    public String toString() {
        return "Fuga [id=" + id + ", name=" + name + "]";
    }
}
実行結果
{"list":[{"@type":"HogeHoge","id":10,"name":"hoge"},{"@type":"FugaFuga","id":20,"name":"fuga"}]}

参考