Serializando enums com Jackson

90

Eu tenho um Enum descrito abaixo:

public enum OrderType {

  UNKNOWN(0, "Undefined"),
  TYPEA(1, "Type A"),
  TYPEB(2, "Type B"),
  TYPEC(3, "Type C");

  private Integer id;
  private String name;

  private WorkOrderType(Integer id, String name) {
    this.id = id;
    this.name = name;
  }

  //Setters, getters....
}

Devolvo a matriz enum com meu controlador ( new OrderType[] {UNKNOWN,TYPEA,TYPEB,TYPEC};) e o Spring o serializa na seguinte string json:

["UNKNOWN", "TYPEA", "TYPEB", "TYPEC"] 

Qual é a melhor abordagem para forçar Jackson a serializar enums exatamente como POJOs? Por exemplo:

[
  {"id": 1, "name": "Undefined"},
  {"id": 2, "name": "Type A"},
  {"id": 3, "name": "Type B"},
  {"id": 4, "name": "Type C"}
]

Joguei com anotações diferentes, mas não consegui obter esse resultado.

Sem destino
fonte
1
Parece que você já encontrou a solução; ótimo! Estava curioso para saber por que você precisa disso?
StaxMan de
Estou desenvolvendo um aplicativo GWT que se comunica com o lado do servidor via JSON. Este enum fornecerá valores de opção para o combobox.
Nofate,
Ah ok. Então meio que abreviatura para conjunto de valores ... interessante.
StaxMan de

Respostas:

87

Finalmente encontrei a solução sozinho.

Tive que anotar enum @JsonSerialize(using = OrderTypeSerializer.class)e implementar serializador personalizado:

public class OrderTypeSerializer extends JsonSerializer<OrderType> {

  @Override
  public void serialize(OrderType value, JsonGenerator generator,
            SerializerProvider provider) throws IOException,
            JsonProcessingException {

    generator.writeStartObject();
    generator.writeFieldName("id");
    generator.writeNumber(value.getId());
    generator.writeFieldName("name");
    generator.writeString(value.getName());
    generator.writeEndObject();
  }
}
Sem destino
fonte
4
Observe que para configurar Jackson para usar o processamento de (des) serialização personalizado, uma alternativa ao uso de uma anotação é registrar (des) serializadores com um módulo de configuração. wiki.fasterxml.com/JacksonHowToCustomSerializers
Programador Bruce
1
Isso não funcionou para mim usando Spring 3.1.1. Meu @Controller ainda retorna json sem meus atributos.
Dave
Eu tenho alguns enums, e quero obter todos os enums com uma função. Como eu posso fazer isso?
Morteza Malvandi
Para um tipo de enum, preciso definir um desserializador personalizado. Existe alguma solução genérica?
Chao
78
@JsonFormat(shape= JsonFormat.Shape.OBJECT)
public enum SomeEnum

disponível desde https://github.com/FasterXML/jackson-databind/issues/24

acabei de testar, funciona com a versão 2.1.2

resposta para TheZuck :

Eu tentei seu exemplo, peguei Json:

{"events":[{"type":"ADMIN"}]}

Meu código:

@RequestMapping(value = "/getEvent") @ResponseBody
  public EventContainer getEvent() {
    EventContainer cont = new EventContainer();
    cont.setEvents(Event.values());
    return cont;
 }

class EventContainer implements Serializable {

  private Event[] events;

  public Event[] getEvents() {
    return events;
 }

 public void setEvents(Event[] events) {
   this.events = events;
 }
}

e as dependências são:

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-annotations</artifactId>
  <version>${jackson.version}</version>
</dependency>

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-core</artifactId>
  <version>${jackson.version}</version>
</dependency>

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>${jackson.version}</version>
  <exclusions>
    <exclusion>
      <artifactId>jackson-annotations</artifactId>
      <groupId>com.fasterxml.jackson.core</groupId>
    </exclusion>
    <exclusion>
      <artifactId>jackson-core</artifactId>
      <groupId>com.fasterxml.jackson.core</groupId>
    </exclusion>
  </exclusions>
</dependency>

<jackson.version>2.1.2</jackson.version>
Vecnas
fonte
2
Gosto dessa alternativa, é mais limpa, porém, tentei com essa classe e o tipo não serializa, alguma ideia o que há de errado? @JsonFormat (shape = JsonFormat.Shape.OBJECT) @JsonAutoDetect () public enum Event {VISIT_WEBSITE (Type.ADMIN); @JsonProperty public Type type; public Type getType () {return type; } Evento (tipo tipo) {this.type = tipo; } public enum Type {ADMIN, CONSUMER,}} Estou usando Jackson 2.1.2
TheZuck
Eu adicionei detalhes adicionais ao corpo da resposta
Vecnas
descobri o que estava errado, eu estava usando Jackson 2.1.2, mas minha versão Spring ainda era 3.1, portanto, não era compatível com esta versão. Atualizado para 3.2.1 e tudo está bem agora. Obrigado!
TheZuck
@Vecnas Posso substituir o padrão @JsonFormatdo enum quando ele é usado em outra entidade? por exemplo, uma entidade na qual desejo que o enum seja serializado como uma string em vez de um objeto. tento adicionar outro @JsonFormatno campo da classe que usa o enum, mas sempre serializado como um objeto.
herau
O que eu encontrei, use - @JsonSerialize (using = ToStringSerializer.class) para um campo, ele usa toString (). Não é uma solução estrita, mas funciona
Vecnas,
25

Eu encontrei uma solução muito boa e concisa, especialmente útil quando você não pode modificar as classes enum como era no meu caso. Em seguida, você deve fornecer um ObjectMapper customizado com um determinado recurso ativado. Esses recursos estão disponíveis desde Jackson 1.6.

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

Existem mais recursos relacionados a enum disponíveis, veja aqui:

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

Lagivan
fonte
4
Concordo. Além disso, no Jackson 2.5, você não precisa de um mapeador de objeto personalizado. Basta fazer isto: objMapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);e isto:objMapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING);
Jake Toronto
14

Aqui está minha solução. Eu quero transformar enum em {id: ..., name: ...}forma.

Com Jackson 1.x :

pom.xml:

<properties>
    <jackson.version>1.9.13</jackson.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.codehaus.jackson</groupId>
        <artifactId>jackson-core-asl</artifactId>
        <version>${jackson.version}</version>
    </dependency>
    <dependency>
        <groupId>org.codehaus.jackson</groupId>
        <artifactId>jackson-mapper-asl</artifactId>
        <version>${jackson.version}</version>
    </dependency>
</dependencies>

Rule.java:

import org.codehaus.jackson.map.annotate.JsonSerialize;
import my.NamedEnumJsonSerializer;
import my.NamedEnum;

@Entity
@Table(name = "RULE")
public class Rule {
    @Column(name = "STATUS", nullable = false, updatable = true)
    @Enumerated(EnumType.STRING)
    @JsonSerialize(using = NamedEnumJsonSerializer.class)
    private Status status;
    public Status getStatus() { return status; }
    public void setStatus(Status status) { this.status = status; }

    public static enum Status implements NamedEnum {
        OPEN("open rule"),
        CLOSED("closed rule"),
        WORKING("rule in work");

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

NamedEnum.java:

package my;

public interface NamedEnum {
    String name();
    String getName();
}

NamedEnumJsonSerializer.java:

package my;

import my.NamedEnum;
import java.io.IOException;
import java.util.*;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.map.JsonSerializer;
import org.codehaus.jackson.map.SerializerProvider;

public class NamedEnumJsonSerializer extends JsonSerializer<NamedEnum> {
    @Override
    public void serialize(NamedEnum value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
        Map<String, String> map = new HashMap<>();
        map.put("id", value.name());
        map.put("name", value.getName());
        jgen.writeObject(map);
    }
}

Com Jackson 2.x :

pom.xml:

<properties>
    <jackson.version>2.3.3</jackson.version>
</properties>

<dependencies>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-core</artifactId>
        <version>${jackson.version}</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>${jackson.version}</version>
    </dependency>
</dependencies>

Rule.java:

import com.fasterxml.jackson.annotation.JsonFormat;

@Entity
@Table(name = "RULE")
public class Rule {
    @Column(name = "STATUS", nullable = false, updatable = true)
    @Enumerated(EnumType.STRING)
    private Status status;
    public Status getStatus() { return status; }
    public void setStatus(Status status) { this.status = status; }

    @JsonFormat(shape = JsonFormat.Shape.OBJECT)
    public static enum Status {
        OPEN("open rule"),
        CLOSED("closed rule"),
        WORKING("rule in work");

        private String name;
        Status(String name) { this.name = name; }
        public String getName() { return this.name; }
        public String getId() { return this.name(); }
    };
}

Rule.Status.CLOSEDtraduzido para {id: "CLOSED", name: "closed rule"}.

Gavenkoa
fonte
Excelente. Você salvou meu dia :-)
sriram
4

Uma maneira fácil de serializar Enum é usando a anotação @JsonFormat. @JsonFormat pode configurar a serialização de um Enum de três maneiras.

@JsonFormat.Shape.STRING
public Enum OrderType {...}

usa OrderType :: name como o método de serialização. A serialização de OrderType.TypeA é“TYPEA”

@JsonFormat.Shape.NUMBER
Public Enum OrderTYpe{...}

usa OrderType :: ordinal como o método de serialização. A serialização de OrderType.TypeA é1

@JsonFormat.Shape.OBJECT
Public Enum OrderType{...}

trata OrderType como um POJO. A serialização de OrderType.TypeA é{"id":1,"name":"Type A"}

JsonFormat.Shape.OBJECT é o que você precisa no seu caso.

Uma maneira um pouco mais complicada é a sua solução, especificando um serializador para o Enum.

Confira esta referência: https://fasterxml.github.io/jackson-annotations/javadoc/2.2.0/com/fasterxml/jackson/annotation/JsonFormat.html

raio
fonte
3

Use a anotação @JsonCreator, crie o método getType (), é serializar com toString ou objeto funcionando

{"ATIVO"}

ou

{"type": "ATIVO", "descricao": "Ativo"}

...

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.JsonNodeType;

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

    ATIVO("Ativo"),
    PENDENTE_VALIDACAO("Pendente de Validação"),
    INATIVO("Inativo"),
    BLOQUEADO("Bloqueado"),
    /**
     * Usuarios cadastrados pelos clientes que não possuem acesso a aplicacao,
     * caso venham a se cadastrar este status deve ser alterado
     */
    NAO_REGISTRADO("Não Registrado");

    private SituacaoUsuario(String descricao) {
        this.descricao = descricao;
    }

    private String descricao;

    public String getDescricao() {
        return descricao;
    }

    // TODO - Adicionar metodos dinamicamente
    public String getType() {
        return this.toString();
    }

    public String getPropertieKey() {
        StringBuilder sb = new StringBuilder("enum.");
        sb.append(this.getClass().getName()).append(".");
        sb.append(toString());
        return sb.toString().toLowerCase();
    }

    @JsonCreator
    public static SituacaoUsuario fromObject(JsonNode node) {
        String type = null;
        if (node.getNodeType().equals(JsonNodeType.STRING)) {
            type = node.asText();
        } else {
            if (!node.has("type")) {
                throw new IllegalArgumentException();
            }
            type = node.get("type").asText();
        }
        return valueOf(type);
    }

}
Gleidosn
fonte
0

No Spring Boot 2, a maneira mais fácil é declarar em seu application.properties:

spring.jackson.serialization.WRITE_ENUMS_USING_TO_STRING=true
spring.jackson.deserialization.READ_ENUMS_USING_TO_STRING=true

e definir o método toString () de seus enums.

JRA_TLL
fonte