Sérialisation des énumérations avec Jackson

90

J'ai un Enum décrit ci-dessous:

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

Je retourne le tableau enum avec mon contrôleur ( new OrderType[] {UNKNOWN,TYPEA,TYPEB,TYPEC};), et Spring le sérialise dans la chaîne json suivante:

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

Quelle est la meilleure approche pour forcer Jackson à sérialiser des énumérations comme les POJO? Par exemple:

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

J'ai joué avec différentes annotations mais je n'ai pas réussi à obtenir un tel résultat.

Pas de destin
la source
1
Il semble que vous ayez déjà trouvé la solution; génial! Étiez-vous curieux de savoir pourquoi vous en avez besoin?
StaxMan
Je développe une application GWT qui communique avec le serveur via JSON. Cette énumération fournira des valeurs d'option pour la zone de liste déroulante.
Nofate du
Ah ok. Donc une sorte de raccourci pour un ensemble de valeurs ... intéressant.
StaxMan

Réponses:

87

Enfin j'ai trouvé la solution moi-même.

J'ai dû annoter enum @JsonSerialize(using = OrderTypeSerializer.class)et implémenter un sérialiseur personnalisé:

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();
  }
}
Pas de destin
la source
4
Notez que pour configurer Jackson pour utiliser le traitement de (dé) sérialisation personnalisé, une alternative à l'utilisation d'une annotation est d'enregistrer (dé) sérialiseurs avec un module de configuration. wiki.fasterxml.com/JacksonHowToCustomSerializers
Programmeur Bruce
1
Cela n'a pas fonctionné pour moi avec Spring 3.1.1. Mon @Controller renvoie toujours json sans mes attributs.
Dave
J'ai des énumérations et je veux obtenir toutes les énumérations avec une fonction. Comment puis-je le faire?
Morteza Malvandi
Pour un type d'énumération, je dois définir un désérialiseur personnalisé. Existe-t-il une solution générique?
Chao
78
@JsonFormat(shape= JsonFormat.Shape.OBJECT)
public enum SomeEnum

disponible depuis https://github.com/FasterXML/jackson-databind/issues/24

vient de tester cela fonctionne avec la version 2.1.2

réponse à TheZuck :

J'ai essayé votre exemple, j'ai obtenu Json:

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

Mon code:

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

et les dépendances sont:

<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
la source
2
J'aime cette alternative, elle est plus propre, cependant, je l'ai essayée avec cette classe et le type n'est pas sérialisé, une idée de ce qui ne va pas? @JsonFormat (shape = JsonFormat.Shape.OBJECT) @JsonAutoDetect () public enum Event {VISIT_WEBSITE (Type.ADMIN); @JsonProperty public Type type; public Type getType () {type de retour; } Événement (type de type) {this.type = type; } public enum Type {ADMIN, CONSUMER,}} J'utilise Jackson 2.1.2
TheZuck
J'ai ajouté des détails supplémentaires au corps de la réponse
Vecnas
a découvert ce qui n'allait pas, j'utilisais Jackson 2.1.2 mais ma version Spring était toujours 3.1 et ne supportait donc pas cette version. Mise à niveau vers 3.2.1 et tout va bien maintenant. Merci!
TheZuck
@Vecnas Puis-je remplacer la valeur par défaut @JsonFormatde l'énumération lorsqu'elle est utilisée dans une autre entité? par exemple, une entité dans laquelle je souhaite que l'énumération soit sérialisée sous forme de chaîne au lieu d'un objet. J'essaye d'en ajouter un autre @JsonFormatdans le champ dans la classe qui utilise l'énumération mais il est toujours sérialisé en tant qu'objet.
herau
Ce que j'ai trouvé, utilisez - @JsonSerialize (en utilisant = ToStringSerializer.class) pour un champ, il utilise toString (). Solution pas stricte, mais fonctionne
Vecnas
25

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.

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 à l'énumération disponibles, voir ici:

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

Lagivan
la source
4
Je suis d'accord. De plus, dans Jackson 2.5, vous n'avez pas besoin d'un mappeur d'objets personnalisé. Faites juste ceci: objMapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);et ceci:objMapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING);
Jake Toronto
14

Voici ma solution. Je veux que transform enum se {id: ..., name: ...}forme.

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

Avec 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.CLOSEDtraduit en {id: "CLOSED", name: "closed rule"}.

gavenkoa
la source
Excellent. Vous avez sauvé ma journée :-)
sriram
4

Un moyen simple de sérialiser Enum consiste à utiliser l'annotation @JsonFormat. @JsonFormat peut configurer la sérialisation d'un Enum de trois manières.

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

utilise OrderType :: name comme méthode de sérialisation. La sérialisation de OrderType.TypeA est“TYPEA”

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

utilise OrderType :: ordinal comme méthode de sérialisation. La sérialisation de OrderType.TypeA est1

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

traite OrderType comme un POJO. La sérialisation de OrderType.TypeA est{"id":1,"name":"Type A"}

JsonFormat.Shape.OBJECT est ce dont vous avez besoin dans votre cas.

Une manière un peu plus compliquée est votre solution, en spécifiant un sérialiseur pour Enum.

Consultez cette référence: https://fasterxml.github.io/jackson-annotations/javadoc/2.2.0/com/fasterxml/jackson/annotation/JsonFormat.html

rayon
la source
3

Utilisez l'annotation @JsonCreator, créez la méthode getType (), sérialisez avec toString ou l'objet fonctionne

{"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
la source
0

Dans Spring Boot 2, le moyen le plus simple est de déclarer dans votre application.properties:

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

et définissez la méthode toString () de vos énumérations.

JRA_TLL
la source