Jackson enum Serializing and DeSerializer


225

Sto usando JAVA 1.6 e Jackson 1.9.9 Ho 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;
    }
}

Ho aggiunto un @JsonValue, questo sembra fare il lavoro in cui serializza l'oggetto in:

{"event":"forgot password"}

ma quando provo a deserializzare ottengo 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

Cosa mi sto perdendo qui?


4
Ci hai provato {"Event":"FORGOT_PASSWORD"}? Nota i tappi su Event e FORGOT_PASSWORD.
OldCurmudgeon,


Chi è venuto qui di controllare anche la sintassi setter getter se si seguono diversa convenzione di denominazione cioè, invece di getValuequesto GetValuenon funziona
Davut Gürbüz

Risposte:


287

La soluzione serializzatore / deserializzatore indicata da @xbakesx è eccellente se si desidera disaccoppiare completamente la classe enum dalla sua rappresentazione JSON.

In alternativa, se si preferisce una soluzione autonoma, un'implementazione basata su @JsonCreatore @JsonValueannotazioni sarebbe più conveniente.

Quindi, sfruttando l'esempio di @Stanley, la seguente è una soluzione completa e indipendente (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
    }
}

@Agusti si prega di dare un'occhiata a mia domanda, che cosa mi manca ci stackoverflow.com/questions/30525986/enum-is-not-binding
Prabjot Singh

25
forse ovvio per alcuni, ma nota che @ JsonValue è usato per la serializzazione e @ JsonCreator per la deserializzazione. Se non stai facendo entrambe le cose, avrai solo bisogno dell'una o dell'altra.
acvcu,

6
Non mi piace molto questa soluzione per il semplice fatto che tu introduci due fonti di verità. Lo sviluppatore dovrà sempre ricordare di aggiungere il nome in due punti. Preferisco di gran lunga una soluzione che fa la cosa giusta senza decorare gli interni di un enum con una mappa.
mttdbrd,

2
@ttdbrd che ne dici di unificare le verità? gist.github.com/Scuilion/036c53fd7fee2de89701a95822c0fb60
KevinO

1
Invece di una mappa statica puoi usare YourEnum.values ​​() che fornisce Array of YourEnum e iterate su di esso
Valeriy K.

209

Nota che a partire da questo commit nel giugno 2015 (Jackson 2.6.2 e versioni successive) ora puoi semplicemente scrivere:

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

1
bella soluzione. È un peccato che io sia bloccato con 2.6.0 in bundle in Dropwizard :-(
Clint Eastwood,

1
Sfortunatamente questo non restituisce la proprietà quando converti il ​​tuo enum in una stringa.
Nicola,

4
Questa funzione è stata deprecata dal 2.8.
pqian,

2
Questa soluzione funziona sia per serializzare che per deserializzare su Enum. Testato in 2.8.
Downhillski,


88

Dovresti creare un metodo factory statico che accetta un singolo argomento e annotalo @JsonCreator(disponibile da Jackson 1.2)

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

Maggiori informazioni sull'annotazione JsonCreator qui .


10
Questa è la soluzione più pulita e concisa, il resto sono solo tonnellate di scaldabagno che potrebbero (e dovrebbero!) Essere evitati a tutti i costi!
Clint Eastwood,

4
@JSONValueserializzare e @JSONCreatordeserializzare.
Chiranjib,

@JsonCreator public static Event valueOf(int intValue) { ... }di deserializzare inta Eventenumerator.
Eido95,

1
@ClintEastwood se le altre soluzioni debbano essere evitate dipende se si desidera separare o meno le preoccupazioni di serializzazione / deserializzazione dall'enum.
Asa,

44

Risposta effettiva:

Il deserializzatore predefinito per enum usa .name()per deserializzare, quindi non sta usando @JsonValue. Quindi, come ha sottolineato @OldCurmudgeon, dovresti passare {"event": "FORGOT_PASSWORD"}per corrispondere al .name()valore.

Un'altra opzione (supponendo che i valori json di scrittura e lettura siano gli stessi) ...

Ulteriori informazioni:

C'è (ancora) un altro modo per gestire il processo di serializzazione e deserializzazione con Jackson. È possibile specificare queste annotazioni per utilizzare il proprio serializzatore e deserializzatore personalizzati:

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

Quindi devi scrivere MySerializere MyDeserializerche assomigliano a questo:

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

}

Ultimo po ', in particolare per fare questo a un enum JsonEnumche serializza con il metodo getYourValue(), il tuo serializzatore e deserializzatore potrebbero apparire così:

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
L'uso del serializzatore (de) personalizzato uccide la semplicità (che utilizza Jackson vale la pena, tra l'altro), quindi è necessario in situazioni molto pesanti. Usa @JsonCreator, come descritto di seguito, e controlla questo commento
Dmitry Gryazin,

1
Questo soluiton è il migliore per il problema un po 'folle introdotto nella domanda dei PO. Il vero problema qui è che l'OP vuole restituire i dati strutturati in una forma renderizzata . Cioè, stanno restituendo dati che includono già una stringa intuitiva. Ma per trasformare il modulo reso in un identificatore, abbiamo bisogno di un codice che possa invertire la trasformazione. La risposta hacky accettata vuole utilizzare una mappa per gestire la trasformazione, ma richiede più manutenzione. Con questa soluzione, puoi aggiungere nuovi tipi enumerati e quindi i tuoi sviluppatori possono andare avanti con i loro lavori.
mttdbrd,

34

Ho trovato una soluzione molto bella e concisa, utile soprattutto quando non puoi modificare le classi enum come nel mio caso. Quindi è necessario fornire un ObjectMapper personalizzato con una determinata funzione abilitata. Queste funzionalità sono disponibili da Jackson 1.6. Quindi devi solo scrivere il toString()metodo nel tuo 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);
    }
}

Ci sono più funzioni relative all'enum disponibili, vedi qui:

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


10
non sono sicuro del motivo per cui è necessario estendere la classe. puoi abilitare questa funzione su un'istanza di ObjectMapper.
mttdbrd,

+1 perché mi ha indicato [READ | WRITE] _ENUMS_USING_TO_STRING che posso usare in Spring application.yml
HelLViS69

8

Prova questo.

public enum Event {

    FORGOT_PASSWORD("forgot password");

    private final String value;

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

    private Event() {
        this.value = this.name();
    }

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

6

È possibile personalizzare la deserializzazione per qualsiasi attributo.

Dichiara la tua classe deserialize usando l'annotazioneJsonDeserialize ( import com.fasterxml.jackson.databind.annotation.JsonDeserialize) per l'attributo che verrà elaborato. Se questo è un Enum:

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

In questo modo la tua classe verrà utilizzata per deserializzare l'attributo. Questo è un esempio completo:

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

Nathaniel Ford, è migliorato?
Fernando Gomes,

1
Sì, questa è una risposta molto migliore; fornisce un certo contesto. Vorrei andare ancora oltre e discutere del perché l'aggiunta della deserializzazione in questo modo risolva l'ostacolo specifico del PO.
Nathaniel Ford,

5

Esistono vari approcci che è possibile adottare per realizzare la deserializzazione di un oggetto JSON in un enum. Il mio stile preferito è creare una classe interiore:

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

Ecco un altro esempio che utilizza i valori di stringa anziché una mappa.

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

Nel contesto di un enum, l'utilizzo di @JsonValuenow (da 2.0) funziona per serializzazione e deserializzazione.

Secondo le annotazioni jackson javadoc per@JsonValue :

NOTA: quando si usa per enumerazioni Java, una caratteristica aggiuntiva è che il valore restituito dal metodo annotato è anche considerato il valore da cui deserializzare, non solo come stringa JSON da serializzare. Ciò è possibile poiché l'insieme dei valori di Enum è costante ed è possibile definire la mappatura, ma non può essere fatto in generale per i tipi POJO; come tale, questo non viene utilizzato per la deserializzazione di POJO.

Quindi avere l' Eventenumazione annotata proprio come sopra funziona (sia per la serializzazione che per la deserializzazione) con jackson 2.0+.


3

Oltre a utilizzare @JsonSerialize @JsonDeserialize, è anche possibile utilizzare SerializationFeature e DeserializationFeature (jackson binding) nel mappatore oggetti.

Come DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE, che fornisce il tipo enum predefinito se quello fornito non è definito nella classe enum.


0

Il modo più semplice che ho trovato è usare l'annotazione @ JsonFormat.Shape.OBJECT per l'enum.

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

0

Nel mio caso, questo è ciò che ha risolto:

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;

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

    DAILY(1),
    WEEKLY(2),
    ;

    private final int id;

    PeriodEnum(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return this.name();
    }

    @JsonCreator
    public static PeriodEnum fromJson(@JsonProperty("name") String name) {
        return valueOf(name);
    }
}

Serializza e deserializza il seguente json:

{
  "id": 2,
  "name": "WEEKLY"
}

Spero possa essere d'aiuto!

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.