Jackson とは?
Java 用の JSON パーサーライブラリの1つ。
Java オブジェクトと JSON の相互変換とかができる。
Jersey などで利用されてて、よく見かける気がする。
デファクトスタンダード?
インストール
dependencies {
compile 'com.fasterxml.jackson.core:jackson-databind:2.3.4'
}
Hello World
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 のいずれかをアノテートすれば、もう一方のメソッドをアノテートする必要はない。
プロパティの名前を変更する
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 のフィールド名が使用される。
任意のフィールドを変換の対象外にする
個々のフィールドで指定する
package sample.jackson;
import com.fasterxml.jackson.annotation.JsonIgnore;
public class Hoge {
public int id;
@JsonIgnore
public String name;
}
{"id":10}
@JsonIgnore
を設定すると、そのフィールドが変換の対象外になる。
まとめて設定する
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 オブジェクトに存在しないプロパティがある場合、実行時にエラーが発生する。
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 を設定する。
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]
デシリアライズ時のコンストラクタ(ファクトリメソッド)を指定する
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
をアノテートすることもできる。
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 に出力する
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
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 属性
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"}
型情報のプロパティ名を指定できる。
include
が WRAPPER_ARRAY
または WRAPPER_OBJECT
の場合は無視される。
型情報が
ないとき~
package sample.jackson;
public abstract class AbstractClass {
public int id;
public String name;
}
package sample.jackson;
public class Hoge extends AbstractClass {
@Override
public String toString() {
return "Hoge [id=" + id + ", name=" + name + "]";
}
}
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
のインスタンスを生成しようとして、エラーになってしまう。
あるとき~
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]]]
ちゃんと具象クラスのインスタンスが生成されている。
型情報の名前を任意の値に変更する
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 に出力するときの値を指定できる。
その場合、 @JsonTypeInfo
の use
属性は、 NAME
にしておく必要がある。
各具象クラスごとに名前を設定する
しかし、この方法だと抽象クラスに具象クラスの情報が載ってしまい、行儀が悪い。
そんなときは、 @JsonTypeName
を使えば、各具象クラスごとに名前を設定できる。
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 + "]";
}
}
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"}]}
参考
- FasterXML/jackson-databind
- Jackson Annotations · FasterXML/jackson-annotations Wiki
- java - what's the difference between jackson-core-asl and jackson-core-lgpl - Stack Overflow
- Issue when trying to use Jackson in java - Stack Overflow
- json - Jackson JsonTypeInfo.As.EXTERNAL_PROPERTY doesn't work as expected - Stack Overflow