Comment sérialiser des enums en tant qu’objets JSON avec Jackson

Comment sérialiser et désérialiser des enums avec Jackson

1. Vue d'ensemble

Ce tutoriel rapide montrera comment contrôler la façon dontJava Enums are serialized and deserialized with Jackson 2.

Pour creuser un peu plus et apprendreother cool things we can do Jackson 2 - rendez-vous surthe main Jackson tutorial.

2. Contrôle de la représentation enum

Définissons l’énumération suivante:

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. Sérialisation des énumérations en JSON

3.1. Représentation enum par défaut

Par défaut, Jackson représentera Java Enums en tant que chaîne simple - par exemple:

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

Aura pour résultat:

"MILE"

Ce que nous aimerions obtenir lors du marshaling de ceEnum to a JSON Object est de donner quelque chose comme:

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

3.2. Enum en tant qu'objet JSON

À partir de Jackson 2.1.2, il existe maintenant une option de configuration qui peut gérer ce type de représentation. Cela peut être fait via l'annotation@JsonFormat au niveau de la classe:

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

Cela conduira au résultat souhaité lors de la sérialisation de ceenum pourDistance.MILE:

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

3.3. Enums et@JsonValue

Un autre moyen simple de contrôler la sortie de marshaling pour une énumération consiste à utiliser l'annotation@JsonValue sur un getter:

public enum Distance {
    ...

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

Ce que nous exprimons ici, c'est quegetMeters() est la représentation réelle de cette énumération. Donc, le résultat de la sérialisation sera:

1609.34

3.4. Sérialiseur personnalisé pour Enum

Avant Jackson 2.1.2, ou si encore plus de personnalisation est requise pour l'énumération, nous pouvons utiliser uncustom Jackson serializer. Tout d'abord, nous devrons le définir:

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

Nous allons maintenant appliquer le sérialiseur à la classe qui sera sérialisée:

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

Ce qui résulte en:

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

4. Deserializing JSON to Enum

Tout d'abord, définissons une classeCity qui a un membreDistance:

public class City {

    private Distance distance;
    ...
}

Ensuite, nous aborderons les différentes façons de désérialiser une chaîne JSON en un Enum.

4.1. Comportement par défaut

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

Par exemple, cela désérialisera le JSON:

{"distance":"KILOMETER"}

Vers un objetDistance.KILOMETER:

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

4.2. Utilisation de@JsonValue

Nous avons appris à utiliser@JsonValue pour sérialiser les Enums. Nous pouvons également utiliser la même annotation pour la désérialisation. Cela est possible car les valeurs Enum sont des constantes.

Tout d’abord, utilisons@JsonValue avec l’une des méthodes getter -getMeters():

public enum Distance {
    ...

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

Désormais, la valeur de retour de la méthodegetMeters() représente les objets Enum. Ainsi, lors de la désérialisation de l'exemple JSON:

{"distance":"0.0254"}

Jackson recherchera l'objet Enum qui a une valeur de retourgetMeters() de 0,0254. Dans ce cas, l'objet estDistance.INCH:

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

4.3. Utilisation de@JsonProperty

L'annotation@JsonProperty est utilisée sur les instances d'énumération:

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

    ...
}

En utilisant cette annotation,we are simply telling Jackson to map the value of the @JsonProperty to the object annotated with this value.

À la suite de la déclaration ci-dessus, l'exemple de chaîne JSON:

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

Sera mappé à l'objetDistance.KILOMETER:

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

4.4. Utilisation de@JsonCreator

Jackson invoque des méthodes annotées avec@JsonCreator pour obtenir une instance de la classe englobante.

Considérons la représentation JSON:

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

Maintenant, définissons la méthode d'usineforValues() avec l'annotation@JsonCreator:

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

    ...
}

Notez l'utilisation de l'annotation@JsonProperty pour lier les champs JSON avec les arguments de méthode.

Ensuite, lorsque nous désérialiserons l'exemple JSON, nous obtiendrons le résultat:

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

4.5. Utilisation d'unDeserializer personnalisé

Un désérialiseur personnalisé peut être utilisé si aucune des techniques décrites n'est disponible. Par exemple, il se peut que nous n’ayons pas accès au code source Enum ou que nous utilisions une ancienne version de Jackson qui ne prend pas en charge une ou plusieurs des annotations couvertes jusqu’à présent.

Selonour custom deserialization article, afin de désérialiser le JSON fourni dans la section précédente, nous allons commencer par créer la classe de désérialisation:

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

Ensuite, nous utilisons l'annotation@JsonDeserialize sur Enum pour spécifier notre désérialiseur personnalisé:

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

Et notre résultat est:

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

5. Conclusion

Cet article a illustré comment mieux contrôler lesserialization and deserialization processes and formats of Java Enums.

L'implémentation de tous ces exemples et extraits de code peut être trouvéeover on Github.