web-dev-qa-db-fra.com

Jackson enum Sérialisation et désérialisation

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é un @JsonValue, cela semble faire le travail dans lequel l'objet est sérialisé:

{"event":"forgot password"}

mais quand j'essaie de désérialiser je reçois un

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 j'oublie ici?

197
pookieman

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

Alternativement, si vous préférez une solution autonome, une implémentation basée sur les annotations @JsonCreator et @JsonValue serait plus pratique.

En tirant parti de l’exemple, @ Stanley , voici 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
    }
}
254
Agustí Sánchez

Notez que depuis this commit en juin 2015 (Jackson 2.6.2 et versions ultérieures), vous pouvez désormais écrire simplement:

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

Vous devriez créer une méthode de fabrique 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 ici .

84
Stanley

Réponse réelle:

Le désérialiseur par défaut pour les énumérations utilise .name() pour désérialiser. Il n'utilise donc pas @JsonValue. Ainsi, comme @OldCurmudgeon l'a fait remarquer, vous devez passer {"event": "FORGOT_PASSWORD"} pour correspondre à la valeur .name().

Une autre option (en supposant que vous souhaitiez que les valeurs json en écriture et en lecture soient identiques) ...

Plus d'infos:

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

}

Dernier petit détail, en particulier pour effectuer cela sur un enum JsonEnum qui est sérialisé 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;
}
40
xbakesx

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 dans mon cas. Ensuite, vous devez 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 la méthode 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 y a plus de fonctionnalités liées à enum disponibles, voir ici:

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

30
lagivan

Vous pouvez personnaliser la désérialisation pour n'importe quel attribut.

Déclarez votre classe de désérialisation à l'aide de l'annotationJsonDeserialize (import com.fasterxml.jackson.databind.annotation.JsonDeserialize) de l'attribut qui sera traité. Si c'est un Enum:

@JsonDeserialize(using = MyEnumDeserialize.class)
private MyEnum myEnum;

De cette façon, votre classe sera utilisée pour désérialiser l'attribut. Ceci est un exemple complet:

public class MyEnumDeserialize extends JsonDeserializer<MyEnum> {

    @Override
    public MyEnum deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
        JsonNode node = jsonParser.getCodec().readTree(jsonParser);
        MyEnum type = null;
        try{
            if(node.get("attr") != null){
                type = MyEnum.get(Long.parseLong(node.get("attr").asText()));
                if (type != null) {
                    return type;
                }
            }
        }catch(Exception e){
            type = null;
        }
        return type;
    }
}
6
Fernando Gomes

Essaye ça.

 
 public enum Événement {
 
 FORGOT_PASSWORD ("mot de passe oublié"); 
 
 valeur de chaîne finale privée; 
 
 Evénement privé (description finale de la chaîne) {
 this.value = description; 
} 
 
 événement privé () {
 this.value = this.name (); 
} 
 
 @JsonValue 
 final String value () {
 renvoie this.value ; 
} 
} 
4
Durga

Vous pouvez adopter différentes approches pour effectuer la désérialisation d'un objet JSON en un enum. Mon style préféré est de faire une classe intérieure:

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.hibernate.validator.constraints.NotEmpty;

import Java.util.Arrays;
import Java.util.Map;
import Java.util.function.Function;
import Java.util.stream.Collectors;

import static com.fasterxml.jackson.annotation.JsonFormat.Shape.OBJECT;

@JsonFormat(shape = OBJECT)
public enum FinancialAccountSubAccountType {
  MAIN("Main"),
  MAIN_DISCOUNT("Main Discount");

  private final static Map<String, FinancialAccountSubAccountType> ENUM_NAME_MAP;
  static {
    ENUM_NAME_MAP = Arrays.stream(FinancialAccountSubAccountType.values())
      .collect(Collectors.toMap(
        Enum::name,
        Function.identity()));
  }

  private final String displayName;

  FinancialAccountSubAccountType(String displayName) {
    this.displayName = displayName;
  }

  @JsonCreator
  public static FinancialAccountSubAccountType fromJson(Request request) {
    return ENUM_NAME_MAP.get(request.getCode());
  }

  @JsonProperty("name")
  public String getDisplayName() {
    return displayName;
  }

  private static class Request {
    @NotEmpty(message = "Financial account sub-account type code is required")
    private final String code;
    private final String displayName;

    @JsonCreator
    private Request(@JsonProperty("code") String code,
                    @JsonProperty("name") String displayName) {
      this.code = code;
      this.displayName = displayName;
    }

    public String getCode() {
      return code;
    }

    @JsonProperty("name")
    public String getDisplayName() {
      return displayName;
    }
  }
}
4
Sam Berry

Voici un autre exemple qui utilise des valeurs de chaîne au lieu d'une carte.

public enum Operator {
    EQUAL(new String[]{"=","==","==="}),
    NOT_EQUAL(new String[]{"!=","<>"}),
    LESS_THAN(new String[]{"<"}),
    LESS_THAN_EQUAL(new String[]{"<="}),
    GREATER_THAN(new String[]{">"}),
    GREATER_THAN_EQUAL(new String[]{">="}),
    EXISTS(new String[]{"not null", "exists"}),
    NOT_EXISTS(new String[]{"is null", "not exists"}),
    MATCH(new String[]{"match"});

    private String[] value;

    Operator(String[] value) {
        this.value = value;
    }

    @JsonValue
    public String toStringOperator(){
        return value[0];
    }

    @JsonCreator
    public static Operator fromStringOperator(String stringOperator) {
        if(stringOperator != null) {
            for(Operator operator : Operator.values()) {
                for(String operatorString : operator.value) {
                    if (stringOperator.equalsIgnoreCase(operatorString)) {
                        return operator;
                    }
                }
            }
        }
        return null;
    }
}
4
Gremash

Dans le contexte d'une énumération, utiliser @JsonValue maintenant (depuis la version 2.0) fonctionne pour la sérialisation et la désérialisation .

Selon le jackson-annotations javadoc pour @JsonValue :

REMARQUE: en cas d'utilisation de Java enum, une fonctionnalité supplémentaire est que la valeur renvoyée par la méthode annotée est également considérée comme la valeur à partir de laquelle la désérialisation est effectuée, et pas uniquement la chaîne JSON à sérialiser. Cela est possible car l'ensemble des valeurs Enum est constant et qu'il est possible de définir un mappage, mais ne peut généralement pas être effectué pour les types POJO; en tant que tel, ceci n'est pas utilisé pour la désérialisation de POJO.

Ainsi, le Event enum annoté comme ci-dessus fonctionne (pour la sérialisation et la désérialisation) avec jackson 2.0+.

3
Brice Roncace

Outre l'utilisation de @JsonSerialize @JsonDeserialize, vous pouvez également utiliser SerializationFeature et DeserializationFeature (liaison jackson) dans le mappeur d'objets.

Tels que DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE, qui donnent le type enum par défaut si celui fourni n'est pas défini dans la classe enum.

2
Yuantao

Le moyen le plus simple que j'ai trouvé consiste à utiliser l'annotation @ JsonFormat.Shape.OBJECT pour l'énum.

@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum MyEnum{
    ....
}
0
yrazlik