Jackson enum Serializar y DeSerializer


Estoy usando JAVA 1.6 y Jackson 1.9.9 Tengo una enumeración

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

He agregado un @JsonValue, esto parece hacer el trabajo en el que serializa el objeto:

{"event":"forgot password"}

Pero cuando intento deserializar obtengo 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é me estoy perdiendo aquí?

Author: Mason Wan, 2012-09-18

9 answers

La solución serializer / deserializer señalada por xbakesx es excelente si desea desacoplar completamente la clase yor enum de su representación JSON.

Alternativamente, si prefiere una solución autónoma, una implementación basada en anotaciones @JsonCreator y @JsonValue sería más conveniente.

Así que aprovechando el ejemplo de Stanley, lo siguiente es una solución completa autónoma (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
    }
}
 204
Author: Agustí Sánchez,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-05-20 17:31:20

Tenga en cuenta que a partir de este commit en junio de 2015 (Jackson 2.6.2 y superior) ahora puede simplemente escribir:

public enum Event {
    @JsonProperty("forgot password")
    FORGOT_PASSWORD;
}
 146
Author: tif,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-09-22 17:56:59

Debe crear un método de fábrica estático que tome un solo argumento y anótelo con @JsonCreator (disponible desde Jackson 1.2)

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

Lea más sobre la anotación JsonCreator aquí.

 79
Author: Stanley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-07-02 08:21:32

Respuesta efectiva:

El deserializador predeterminado para enums usa .name() para deserializar, por lo que no está usando el @JsonValue. Así que como @ OldCurmudgeon señaló, tendrías que pasar {"event": "FORGOT_PASSWORD"} para que coincida con el valor .name().

Otra opción (suponiendo que desee que los valores de escritura y lectura de json sean los mismos)...

Más información:

Hay (todavía) otra manera de manejar el proceso de serialización y deserialización con Jackson. Puede especificar estos anotaciones para usar su propio serializador y deserializador personalizado:

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

Entonces tienes que escribir MySerializer y MyDeserializer que se ven así:

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

}

Por último, particularmente para hacer esto a una enumeración JsonEnum que se serializa con el método getYourValue(), su serializador y deserializador podrían verse así:

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;
}
 37
Author: xbakesx,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-04-20 03:04:01

He encontrado una solución muy agradable y concisa, especialmente útil cuando no se pueden modificar las clases de enumeración como fue en mi caso. A continuación, debe proporcionar un ObjectMapper personalizado con una determinada característica habilitada. Estas características están disponibles desde Jackson 1.6. Así que solo necesita escribir el método toString() en su enumeración.

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

Hay más funciones relacionadas con enum disponibles, consulte aquí:

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

 25
Author: lagivan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-28 08:34:36

Aquí hay otro ejemplo que usa valores de cadena en lugar de un mapa.

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;
    }
}
 3
Author: Gremash,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-01-29 23:32:16

Hay varios enfoques que puede tomar para lograr la deserialización de un objeto JSON a una enumeración. Mi estilo favorito es hacer una clase interna:

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;
    }
  }
}
 3
Author: Sam Berry,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-31 22:17:42

Puede personalizar la deserialización para cualquier atributo.

Declare su clase deserialize usando el atributo annotationJsonDeserialize (import com.fasterxml.jackson.databind.annotation.JsonDeserialize) para el atributo que será procesado. Si se trata de una enumeración:

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

De esta manera su clase será usada para deserializar el atributo. Este es un ejemplo 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;
    }
}
 1
Author: Fernando Gomes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-12 22:36:09

En el contexto de una enumeración, usar @JsonValue now (desde 2.0) funciona para serialización y deserialización.

De acuerdo con el jackson-anotaciones javadoc para @JsonValue:

NOTA: cuando se usa para enumeraciones Java, una característica adicional es que el valor devuelto por el método anotado también se considera el valor para deserializar, no solo la cadena JSON para serializar como. Esto es posible ya que el conjunto de valores de enumeración es constante y es posible definir la asignación, pero no se puede hacer en general para los tipos de POJO; como tal, esto no se usa para la deserialización de POJO.

Así que tener la enumeración Event anotada igual que lo anterior funciona (tanto para la serialización como para la deserialización) con jackson 2.0+.

 1
Author: Brice Roncace,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-07-20 15:26:03