Edited at

Java enum 列挙型と JSON を Jackson で相互変換する


概要


  • JSON 操作ライブラリの Jackson を使用して、Java の列挙型と JSON をマッピング (データバインディング) するサンプルコードの挙動を確認する


列挙型について

列挙型は Java 5 から導入された。

Java Programming Language Enhancements


Typesafe Enums - This flexible object-oriented enumerated type facility allows you to create enumerated types with arbitrary methods and fields. It provides all the benefits of the Typesafe Enum pattern ("Effective Java," Item 21) without the verbosity and the error-proneness. (JSR 201)


Javaプログラミング言語の拡張機能


Java SE 5.0での拡張機能

型保証された列挙 - この柔軟なオブジェクト指向の列挙型では、任意のメソッドやフィールドを持つ列挙型を作成できます。煩雑さやエラー発生の可能性なしで、型保証された列挙パターン(『Effective Java』第21項)のすべての利点を実現しています。(JSR 201)


列挙型


5.0では、Java(tm)プログラミング言語で列挙型を言語的にサポートしました。列挙のもっとも簡単な形式では、C、C++、およびC#の形式に似ています。

enum Season { WINTER, SPRING, SUMMER, FALL }

しかし、見かけに騙されることもあります。Javaプログラミング言語の列挙は、その他の言語の場合に比べて非常に強力で、拡張された整数以上の機能があります。新しいenum宣言では、完全なクラスを定義します。これは列挙型と呼ばれます。前述の問題をすべて解決するだけでなく、任意のメソッドやフィールドを列挙型に追加したり、任意のインタフェースを実装したりできます。列挙型では、すべてのObjectメソッドについて、品質の高い実装が可能です。ComparableかつSerializableで、直列化形式は、列挙型の任意の変更に対応できるように設計されています。



動作確認環境


  • macOS Mojave

  • OpenJDK 11.0.2

  • IntelliJ IDEA 2018.3.3 (Community Edition)

  • Jackson Core 2.9.9

  • Jackson Annotations 2.9.9

  • Jackson Databind 2.9.9


サンプル1

シンプルな列挙型を定義して switch case で判定する。


ソースコード

public class EnumSample {

public static void main(String[] args) {

// 親クラスは java.lang.Enum になる
System.out.println(MyType.class.getSuperclass().getName());

// Java の enum はC言語のような整数値ではなくクラスベース
System.out.println(getTypeName(MyType.R_TYPE));
System.out.println(getTypeName(MyType.S_TYPE));
}

// MyType という列挙型を定義する
public enum MyType {
R_TYPE, S_TYPE
}

// switch case で判定
public static String getTypeName(MyType myType) {
switch (myType) {
case R_TYPE:
return "あーるタイプ";
case S_TYPE:
return "えすタイプ";
default:
return "なにものでもない";
}
}
}


実行結果

java.lang.Enum

あーるタイプ
えすタイプ


サンプル2

Jackson を使ったサンプル。

JSON をクラスにマッピングしてオブジェクトを生成する。

クラス内には列挙型も含まれる (マッピングには列挙型の定数名を使用)。

また、生成されたオブジェクトを JSON 文字列として出力する。


読み込む JSON ファイル

{

"items": [
{
"name": "Alice",
"type": "A"
},
{
"name": "Bob",
"type": "B"
},
{
"name": "Carol",
"type": "unknown"
}
]
}


ソースコード

メインクラス。

import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;
import java.io.InputStream;

public class JacksonSample {

public static void main(String[] args) throws IOException {

// Jackson で JSON ファイルを読み込む
ObjectMapper mapper = new ObjectMapper();
InputStream json = ClassLoader.getSystemResourceAsStream("data.json");
MyData myData = mapper.readValue(json, MyData.class);
System.out.println(myData);

// Jaskcon で JSON を出力する
mapper.writeValue(System.out, myData);
}
}

JSON とマッピングするためのクラス。

import java.util.List;

import java.util.stream.Collectors;

public class MyData {

public List<Item> items;

public static class Item {

public String name;
public Type type;

// 出力時に中身がわかりやすいように toString メソッドを実装しておく
@Override
public String toString() {
return "name=" + name + ", type=" + type;
}
}

// 列挙型 Type を定義する
// Jackson による JSON 出力時には、定数名が文字列値として出力される
public enum Type {
A, B, O, AB, unknown
}

// 出力時に中身がわかりやすいように toString メソッドを実装しておく
@Override
public String toString() {
return items.stream()
.map(item -> item.toString())
.collect(Collectors.joining(System.lineSeparator()));
}
}


実行結果

name=Alice, type=A

name=Bob, type=B
name=Carol, type=unknown
{"items":[{"name":"Alice","type":"A"},{"name":"Bob","type":"B"},{"name":"Carol","type":"unknown"}]}


サンプル3

Jackson を使ったサンプル。

JSON をクラスにマッピングしてオブジェクトを生成する。

クラス内には列挙型も含まれる (マッピングには列挙型が持つ整数値を使用)。

また、生成されたオブジェクトを JSON 文字列として出力する。


読み込む JSON ファイル

{

"items": [
{
"name": "Alice",
"type": 1
},
{
"name": "Bob",
"type": 2
},
{
"name": "Carol",
"type": 0
}
]
}


ソースコード

メインクラス。

import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;
import java.io.InputStream;

public class JacksonSampleEx {

public static void main(String[] args) throws IOException {

// Jackson で JSON ファイルを読み込む
ObjectMapper mapper = new ObjectMapper();
InputStream json = ClassLoader.getSystemResourceAsStream("dataex.json");
MyDataEx myData = mapper.readValue(json, MyDataEx.class);
System.out.println(myData);

// Jaskcon で JSON を出力する
mapper.writeValue(System.out, myData);
}
}

JSON とマッピングするためのクラス。

import com.fasterxml.jackson.core.JsonGenerator;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializable;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.jsontype.TypeSerializer;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class MyDataEx {

public List<Item> items;

public static class Item {

public String name;

// Jackson による JSON 解析時に、自前の TypeDeserializer クラスを使用する
@JsonDeserialize(using = TypeDeserializer.class)
public Type type;

// 出力時に中身がわかりやすいように toString メソッドを実装しておく
@Override
public String toString() {
return "name=" + name + ", type=" + type;
}
}

// 列挙型 Type を定義する
public enum Type implements JsonSerializable {

// C言語の enum のように整数値で表現したい
A(1), B(2), O(4), AB(8), unknown(0);

private final int code;

// 整数値を持った列挙型にする
Type(int code) {
this.code = code;
}

// 整数値から Type 型のオブジェクトを返す
public static Type getByCode(int code) {
return Arrays.stream(Type.values()).filter(type -> type.code == code).findFirst().orElseThrow();
}

// Jackson による JSON 出力時に、定数名の文字列ではなく、整数値が出力されるようにする
@Override
public void serialize(JsonGenerator jgen, SerializerProvider provider) throws IOException {
jgen.writeNumber(code);
}

// Jackson による JSON 出力時に、定数名の文字列ではなく、整数値が出力されるようにする
@Override
public void serializeWithType(JsonGenerator jgen, SerializerProvider provider, TypeSerializer typeSer) throws IOException {
jgen.writeNumber(code);
}
}

// Jackson による JSON 解析時に使用するクラスを定義する
private static class TypeDeserializer extends JsonDeserializer<Type> {

// Jackson による JSON 解析時に、整数値から Type オブジェクトを生成する
@Override
public Type deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return Type.getByCode(p.getValueAsInt());
}
}

// 出力時に中身がわかりやすいように toString メソッドを実装しておく
@Override
public String toString() {
return items.stream()
.map(item -> item.toString())
.collect(Collectors.joining(System.lineSeparator()));
}
}


実行結果

name=Alice, type=A

name=Bob, type=B
name=Carol, type=unknown
{"items":[{"name":"Alice","type":1},{"name":"Bob","type":2},{"name":"Carol","type":0}]}


参考資料