Jacksonを使って列挙型をJSONオブジェクトとしてシリアライズする方法

Jacksonで列挙型をシリアル化および逆シリアル化する方法

1. 概要

このクイックチュートリアルでは、Java Enums are serialized and deserialized with Jackson 2の方法を制御する方法を示します。

もう少し深く掘り下げてother cool things we can do Jackson 2を学ぶには、the main Jackson tutorialに進んでください。

2. 列挙型表現の制御

次のEnumを定義しましょう。

public enum Distance {
    KILOMETER("km", 1000),
    MILE("miles", 1609.34),
    METER("meters", 1),
    INCH("inches", 0.0254),
    CENTIMETER("cm", 0.01),
    MILLIMETER("mm", 0.001);

    private String unit;
    private final double meters;

    private Distance(String unit, double meters) {
        this.unit = unit;
        this.meters = meters;
    }

    // standard getters and setters
}

3. 列挙型をJSONにシリアル化する

3.1. デフォルトの列挙型表現

デフォルトでは、JacksonはJava Enumを単純な文字列として表します。たとえば、次のようになります。

new ObjectMapper().writeValueAsString(Distance.MILE);

になります:

"MILE"

このEnum to a JSON Objectをマーシャリングするときに取得したいのは、次のようなものを与えることです。

{"unit":"miles","meters":1609.34}

3.2. JSONオブジェクトとして列挙

Jackson 2.1.2から、この種類の表現を処理できる構成オプションが追加されました。 これは、クラスレベルで@JsonFormatアノテーションを介して実行できます。

@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum Distance { ... }

これにより、このenumDistance.MILEにシリアル化するときに、望ましい結果が得られます。

{"unit":"miles","meters":1609.34}

3.3. 列挙型と@JsonValue

列挙型のマーシャリング出力を制御するさらに別の簡単な方法は、ゲッターで@JsonValueアノテーションを使用することです。

public enum Distance {
    ...

    @JsonValue
    public String getMeters() {
        return meters;
    }
}

ここで表現しているのは、getMeters()がこの列挙型の実際の表現であるということです。 したがって、シリアライズの結果は次のようになります。

1609.34

3.4. 列挙型のカスタムシリアライザー

Jackson 2.1.2より前、または列挙型にさらにカスタマイズが必要な場合は、custom Jackson serializer.を使用できます。最初に、次のように定義する必要があります。

public class DistanceSerializer extends StdSerializer {

    public DistanceSerializer() {
        super(Distance.class);
    }

    public DistanceSerializer(Class t) {
        super(t);
    }

    public void serialize(Distance distance, JsonGenerator generator, SerializerProvider provider)
      throws IOException, JsonProcessingException {
        generator.writeStartObject();
        generator.writeFieldName("name");
        generator.writeString(distance.name());
        generator.writeFieldName("unit");
        generator.writeString(distance.getUnit());
        generator.writeFieldName("meters");
        generator.writeNumber(distance.getMeters());
        generator.writeEndObject();
    }
}

次に、シリアライズされるクラスにシリアライザーを適用します。

@JsonSerialize(using = DistanceSerializer.class)
public enum TypeEnum { ... }

結果は次のとおりです。

{"name":"MILE","unit":"miles","meters":1609.34}

4. JSONを列挙型にデシリアライズする

まず、Distanceメンバーを持つCityクラスを定義しましょう。

public class City {

    private Distance distance;
    ...
}

次に、JSON文字列を列挙型に逆シリアル化するさまざまな方法について説明します。

4.1. デフォルトの動作

By default, Jackson will use the Enum name to deserialize from JSON

たとえば、JSONを逆シリアル化します。

{"distance":"KILOMETER"}

Distance.KILOMETERオブジェクトへ:

City city = new ObjectMapper().readValue(json, City.class);
assertEquals(Distance.KILOMETER, city.getDistance());

4.2. @JsonValueの使用

@JsonValueを使用して列挙型をシリアル化する方法を学習しました。 逆シリアル化にも同じアノテーションを使用できます。 これは、列挙値が定数であるため可能です。

まず、getterメソッドの1つであるgetMeters()@JsonValueを使用しましょう。

public enum Distance {
    ...

    @JsonValue
    public double getMeters() {
        return meters;
    }
}

これで、getMeters()メソッドの戻り値はEnumオブジェクトを表します。 したがって、サンプルJSONをデシリアライズする場合:

{"distance":"0.0254"}

Jacksonは、getMeters()の戻り値が0.0254のEnumオブジェクトを探します。 この場合、オブジェクトはDistance.INCHです。

assertEquals(Distance.INCH, city.getDistance());

4.3. @JsonPropertyの使用

@JsonPropertyアノテーションは、列挙インスタンスで使用されます。

public enum Distance {
    @JsonProperty("distance-in-km")
    KILOMETER("km", 1000),
    @JsonProperty("distance-in-miles")
    MILE("miles", 1609.34);

    ...
}

この注釈を使用することにより、we are simply telling Jackson to map the value of the @JsonProperty to the object annotated with this value

上記の宣言の結果、JSON文字列の例:

{"distance": "distance-in-km"}

Distance.KILOMETERオブジェクトにマップされます:

assertEquals(Distance.KILOMETER, city.getDistance());

4.4. @JsonCreatorの使用

Jacksonは、@JsonCreatorアノテーションが付けられたメソッドを呼び出して、囲んでいるクラスのインスタンスを取得します。

JSON表現を検討してください。

{
    "distance": {
        "unit":"miles",
        "meters":1609.34
    }
}

それでは、@JsonCreatorアノテーションを使用してforValues()ファクトリメソッドを定義しましょう。

public enum Distance {

    @JsonCreator
    public static Distance forValues(@JsonProperty("unit") String unit,
      @JsonProperty("meters") double meters) {
        for (Distance distance : Distance.values()) {
            if (distance.unit.equals(unit) && Double.compare(distance.meters, meters) == 0) {
                return distance;
            }
        }

        return null;
    }

    ...
}

JSONフィールドをメソッド引数にバインドするために@JsonPropertyアノテーションを使用していることに注意してください。

次に、JSONサンプルを逆シリアル化すると、次の結果が得られます。

assertEquals(Distance.MILE, city.getDistance());

4.5. カスタムDeserializerの使用

説明されている手法がいずれも使用できない場合は、カスタムデシリアライザーを使用できます。 たとえば、列挙型ソースコードにアクセスできない場合や、これまでに説明した1つ以上のアノテーションをサポートしていない古いバージョンのJacksonを使用している場合があります。

our custom deserialization articleによると、前のセクションで提供されたJSONを逆シリアル化するために、逆シリアル化クラスを作成することから始めます。

public class CustomEnumDeserializer extends StdDeserializer {

    @Override
    public Distance deserialize(JsonParser jsonParser, DeserializationContext ctxt)
      throws IOException, JsonProcessingException {
        JsonNode node = jsonParser.getCodec().readTree(jsonParser);

        String unit = node.get("unit").asText();
        double meters = node.get("meters").asDouble();

        for (Distance distance : Distance.values()) {

            if (distance.getUnit().equals(unit) && Double.compare(distance.getMeters(), meters) == 0) {
                return distance;
            }
        }

        return null;
    }
}

次に、列挙型の@JsonDeserializeアノテーションを使用して、カスタムデシリアライザーを指定します。

@JsonDeserialize(using = CustomEnumDeserializer.class)
public enum Distance {
   ...
}

結果は次のとおりです。

assertEquals(Distance.MILE, city.getDistance());

5. 結論

この記事では、serialization and deserialization processes and formats of Java Enumsをより適切に制御する方法について説明しました。

これらすべての例とコードスニペットの実装は、over on Githubにあります。