https://qiita.com/opengl-8080/items/b613b9b3bc5d796c840c
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); }}
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); }}
説明
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); }}
@JsonProperty
でプロパティの名前を変更できる。
デフォルトは、 Java のフィールド名が使用される。
任意のフィールドを変換の対象外にする
個々のフィールドで指定する
Hoge.java
package sample.jackson;import com.fasterxml.jackson.annotation.JsonIgnore;public class Hoge { public int id; @JsonIgnore public String name;}
@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); }}
@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.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); }}
デフォルトでは、デシリアライズ時にデフォルトコンストラクタが使用される(デフォルトコンストラクタがない場合は例外がスローされる)。
何らかの理由でデフォルトコンストラクタでデシリアライズしてほしくない場合は、 @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"}
型情報のプロパティ名を指定できる。
include
が WRAPPER_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 に出力するときの値を指定できる。
その場合、 @JsonTypeInfo
の use
属性は、 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"}]}