用 Jackson 序列化枚举

我有一个下面描述的 Enum:

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

使用控制器返回 enum 数组(new OrderType[] {UNKNOWN,TYPEA,TYPEB,TYPEC};) ,Spring 将其序列化为以下 json 字符串:

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

什么是强制 Jackson 像 POJO 一样序列化枚举的最佳方法? 例如:

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

我尝试了不同的注释,但没有得到这样的结果。

107949 次浏览

Finally I found solution myself.

I had to annotate enum with @JsonSerialize(using = OrderTypeSerializer.class) and implement custom serializer:

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();
}
}
@JsonFormat(shape= JsonFormat.Shape.OBJECT)
public enum SomeEnum

available since https://github.com/FasterXML/jackson-databind/issues/24

just tested it works with version 2.1.2

answer to TheZuck:

I tried your example, got Json:

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

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

and dependencies are:

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

Here is my solution. I want transform enum to {id: ..., name: ...} form.

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

With 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.CLOSED translated to {id: "CLOSED", name: "closed rule"}.

I've found a very nice and concise solution, especially useful when you cannot modify enum classes as it was in my case. Then you should provide a custom ObjectMapper with a certain feature enabled. Those features are available since 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);
}
}

There are more enum-related features available, see here:

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

Use @JsonCreator annotation, create method getType(), is serialize with toString or object working

{"ATIVO"}

or

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


}

An easy way to serialize Enum is using @JsonFormat annotation. @JsonFormat can configure the serialization of a Enum in three ways.

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

uses OrderType::name as the serialization method. Serialization of OrderType.TypeA is “TYPEA”

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

uses OrderType::ordinal as the serialization method. Serialization of OrderType.TypeA is 1

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

treats OrderType as a POJO. Serialization of OrderType.TypeA is {"id":1,"name":"Type A"}

JsonFormat.Shape.OBJECT is what you need in your case.

A little more complicated way is your solution, specifying a serializer for the Enum.

Check out this reference: https://fasterxml.github.io/jackson-annotations/javadoc/2.2.0/com/fasterxml/jackson/annotation/JsonFormat.html

In Spring Boot 2, the easiest way is to declare in your application.properties:

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

and define the toString() method of your enums.