273 votes

Jackson enum Serializing et DeSerializer

J'utilise JAVA 1.6 et Jackson 1.9.9. J'ai un enum

public enum Event {
    FORGOT_PASSWORD("forgot password");

    private final String value;

    private Event(final String description) {
        this.value = description;
    }

    @JsonValue
    final String value() {
        return this.value;
    }
}

J'ai ajouté une @JsonValue, qui semble faire le travail en sérialisant l'objet :

{"event":"forgot password"}

mais quand j'essaie de désérialiser, j'obtiens une

Caused by: org.codehaus.jackson.map.JsonMappingException: Can not construct instance of com.globalrelay.gas.appsjson.authportal.Event from String value 'forgot password': value not one of declared Enum instance names

Qu'est-ce que je rate ici ?

4 votes

Avez-vous essayé {"Event":"FORGOT_PASSWORD"} ? Notez les majuscules sur Event et FORGOT_PASSWORD.

0 votes

Ceux qui sont venus ici ont également vérifié la syntaxe des getter setter si vous suivez une convention de dénomination différente, c'est-à-dire qu'au lieu de getValue ce GetValue ne fonctionne pas

330voto

Agustí Sánchez Points 250

La solution de sérialiseur/désérialiseur mise en avant par @xbakesx est une excellente solution si vous souhaitez découpler complètement votre enum à partir de sa représentation JSON.

Sinon, si vous préférez une solution autonome, une mise en œuvre basée sur @JsonCreator y @JsonValue Les annotations seraient plus pratiques.

Donc, en s'appuyant sur l'exemple de @Stanley ce qui suit est une solution complète et autonome (Java 6, Jackson 1.9) :

public enum DeviceScheduleFormat {

    Weekday,
    EvenOdd,
    Interval;

    private static Map<String, DeviceScheduleFormat> namesMap = new HashMap<String, DeviceScheduleFormat>(3);

    static {
        namesMap.put("weekday", Weekday);
        namesMap.put("even-odd", EvenOdd);
        namesMap.put("interval", Interval);
    }

    @JsonCreator
    public static DeviceScheduleFormat forValue(String value) {
        return namesMap.get(StringUtils.lowerCase(value));
    }

    @JsonValue
    public String toValue() {
        for (Entry<String, DeviceScheduleFormat> entry : namesMap.entrySet()) {
            if (entry.getValue() == this)
                return entry.getKey();
        }

        return null; // or fail
    }
}

0 votes

@Agusti s'il vous plaît, jetez un coup d'oeil à ma question, qu'est-ce que j'ai manqué ici. stackoverflow.com/questions/30525986/enum-is-not-binding

43 votes

Peut-être évident pour certains, mais notez que @ JsonValue est utilisé pour la sérialisation et @ JsonCreator pour la désérialisation. Si vous ne faites pas les deux, vous n'aurez besoin que de l'un ou l'autre.

0 votes

@Agusti pouvez-vous s'il vous plaît me dire comment je vais utiliser (appeler) cet enum dans mon json.

260voto

tif Points 2491

Notez qu'à partir de cet engagement en juin 2015 (Jackson 2.6.2 et plus), vous pouvez maintenant simplement écrire :

public enum Event {
    @JsonProperty("forgot password")
    FORGOT_PASSWORD;
}

Ce comportement est documenté ici : https://fasterxml.github.io/jackson-annotations/javadoc/2.11/com/fasterxml/jackson/annotation/JsonProperty.html

À partir de Jackson 2.6, cette annotation peut également être utilisée pour modifier la sérialisation des Enum de la manière suivante :

 public enum MyEnum {
      @JsonProperty("theFirstValue") THE_FIRST_VALUE,
      @JsonProperty("another_value") ANOTHER_VALUE;
 }

comme alternative à l'utilisation de l'annotation JsonValue.

1 votes

Belle solution. C'est dommage que je sois coincé avec la version 2.6.0 incluse dans le Dropwizard :-(

0 votes

Cela ne fera que sérialiser, pas désérialiser.

1 votes

Malheureusement, cela ne renvoie pas la propriété lorsque vous convertissez votre enum en chaîne de caractères.

100voto

Stanley Points 1952

Vous devez créer une méthode d'usine statique qui prend un seul argument et l'annoter avec @JsonCreator (disponible depuis Jackson 1.2)

@JsonCreator
public static Event forValue(String value) { ... }

En savoir plus sur l'annotation JsonCreator aquí .

10 votes

C'est la solution la plus propre et la plus concise, les autres ne sont que des tonnes de texte passe-partout qui pourraient (et devraient !) être évitées à tout prix !

5 votes

@JSONValue pour sérialiser et @JSONCreator pour désérialiser.

0 votes

@JsonCreator public static Event valueOf(int intValue) { ... } pour désérialiser int a Event énumérateur.

48voto

xbakesx Points 3419

Réponse réelle :

Le désérialiseur par défaut pour les enums utilise .name() pour désérialiser, de sorte qu'il n'utilise pas la fonction @JsonValue . Donc, comme @OldCurmudgeon l'a fait remarquer, vous devez passer en {"event": "FORGOT_PASSWORD"} pour correspondre à la .name() valeur.

Une autre option (en supposant que vous souhaitez que les valeurs json en écriture et en lecture soient les mêmes)...

Plus d'informations :

Il existe (encore) une autre façon de gérer le processus de sérialisation et de désérialisation avec Jackson. Vous pouvez spécifier ces annotations pour utiliser votre propre sérialiseur et désérialiseur personnalisé :

@JsonSerialize(using = MySerializer.class)
@JsonDeserialize(using = MyDeserializer.class)
public final class MyClass {
    ...
}

Ensuite, vous devez écrire MySerializer y MyDeserializer qui ressemblent à ceci :

MySerializer

public final class MySerializer extends JsonSerializer<MyClass>
{
    @Override
    public void serialize(final MyClass yourClassHere, final JsonGenerator gen, final SerializerProvider serializer) throws IOException, JsonProcessingException
    {
        // here you'd write data to the stream with gen.write...() methods
    }

}

MyDeserializer

public final class MyDeserializer extends org.codehaus.jackson.map.JsonDeserializer<MyClass>
{
    @Override
    public MyClass deserialize(final JsonParser parser, final DeserializationContext context) throws IOException, JsonProcessingException
    {
        // then you'd do something like parser.getInt() or whatever to pull data off the parser
        return null;
    }

}

Dernière petite partie, en particulier pour faire ceci à un enum JsonEnum qui se sérialise avec la méthode getYourValue() votre sérialiseur et votre désérialiseur pourraient ressembler à ceci :

public void serialize(final JsonEnum enumValue, final JsonGenerator gen, final SerializerProvider serializer) throws IOException, JsonProcessingException
{
    gen.writeString(enumValue.getYourValue());
}

public JsonEnum deserialize(final JsonParser parser, final DeserializationContext context) throws IOException, JsonProcessingException
{
    final String jsonValue = parser.getText();
    for (final JsonEnum enumValue : JsonEnum.values())
    {
        if (enumValue.getYourValue().equals(jsonValue))
        {
            return enumValue;
        }
    }
    return null;
}

3 votes

L'utilisation d'un (dé)sérialiseur personnalisé nuit à la simplicité (ce qui est le but de l'utilisation de Jackson, d'ailleurs), et n'est donc nécessaire que dans les situations vraiment difficiles. Utilisez @JsonCreator, comme décrit ci-dessous, et vérifiez que ce commentaire

2 votes

Cette solution est la meilleure pour le problème un peu fou présenté dans la question de l'OP. Le vrai problème ici est que le PO veut retourner les données structurées dans un fichier de type rendu formulaire. C'est-à-dire qu'ils renvoient des données qui contiennent déjà une chaîne de caractères conviviale. Mais pour transformer le formulaire rendu en un identifiant, nous avons besoin d'un code capable d'inverser la transformation. La réponse bidon acceptée veut utiliser une carte pour gérer la transformation, mais nécessite plus de maintenance. Avec cette solution, vous pouvez ajouter de nouveaux types énumérés et vos développeurs peuvent ensuite se consacrer à leur travail.

43voto

lagivan Points 815

J'ai trouvé une solution très agréable et concise, particulièrement utile lorsque vous ne pouvez pas modifier les classes enum, comme c'était le cas pour moi. Vous devez alors fournir un ObjectMapper personnalisé avec une certaine fonctionnalité activée. Ces fonctionnalités sont disponibles depuis Jackson 1.6. Il vous suffit donc d'écrire toString() dans votre enum.

public class CustomObjectMapper extends ObjectMapper {
    @PostConstruct
    public void customConfiguration() {
        // Uses Enum.toString() for serialization of an Enum
        this.enable(WRITE_ENUMS_USING_TO_STRING);
        // Uses Enum.toString() for deserialization of an Enum
        this.enable(READ_ENUMS_USING_TO_STRING);
    }
}

Il existe d'autres fonctionnalités liées aux enum, voir ici :

https://github.com/FasterXML/jackson-databind/wiki/Serialization-Features https://github.com/FasterXML/jackson-databind/wiki/Deserialization-Features

12 votes

Je ne sais pas pourquoi vous devez étendre la classe. Vous pouvez activer cette fonctionnalité sur une instance de l'ObjectMapper.

0 votes

+1 car il m'a indiqué le [READ|WRITE]_ENUMS_USING_TO_STRING que je peux utiliser dans le fichier Spring application.yml.

0 votes

Merci, votre réponse m'a aidé à résoudre mon problème dans Retrofit Si vous voulez utiliser l'ordinal pendant la sérialisation, utilisez SerializationFeature.WRITE_ENUMS_USING_INDEX .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X