1. 概要

このクイックチュートリアルでは、Java列挙型をJackson2でシリアル化および逆シリアル化する方法を制御する方法を学習します。

もう少し深く掘り下げて、 Jackson 2でできる他のクールなことを学ぶには、メインのJacksonチュートリアルに進んでください。

2. 列挙型表現の制御

次の列挙型を定義しましょう:

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はJavaEnumを単純な文字列として表します。 例えば:

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

結果は次のようになります。

"MILE"

ただし、この列挙型をJSONオブジェクトにマーシャリングする場合、次のようなものを取得したいと思います。

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

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

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

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

これにより、この列挙型Distanceにシリアル化するときに望ましい結果が得られます。MILE:

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

3.3. 列挙型と@JsonValue

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

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

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

1609.34

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

2.1.2より前のバージョンのJacksonを使用している場合、または列挙型にさらにカスタマイズが必要な場合は、カスタムJacksonシリアライザーを使用できます。まず、次のように定義する必要があります。

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. デフォルトの動作

デフォルトでは、Jacksonは列挙型名を使用してJSONから逆シリアル化します。

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

{"distance":"KILOMETER"}

Distance.KILOMETER オブジェクトへ:

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

4.2. @JsonValueを使用する

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

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

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);
 
    ...
}

このアノテーションを使用することで、は、@JsonPropertyの値をこの値でアノテーションが付けられたオブジェクトにマップするようにJacksonに指示しているだけです。

上記の宣言の結果として、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;
    }

    ...
}

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

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

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

4.5. カスタムデシリアライザーの使用

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

カスタムデシリアライズの記事によると、前のセクションで提供されたJSONをデシリアライズするために、デシリアライズクラスを作成することから始めます。

public class CustomEnumDeserializer extends StdDeserializer<Distance> {

    @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. 結論

この記事では、JavaEnumsシリアル化および逆シリアル化プロセスとフォーマットをより適切に制御する方法について説明しました。

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