Stay hungry, Stay foolish

0%

jackson&enum

Jackson虽然使用非常方便,但是在处理Enum类型上,还是有不少坑的,此篇算是做一下总结吧

先上一个例子——两个部门之前接口对接,在传参与返参里涉及到Enum的序列化与反序列化

一个协议端口的Enum

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public enum ProbePortEnum {
HTTP(80, "http"),
GRPC(50051, "grpc");

private Integer port;
private String schema;

ProbePortEnum(Integer port, String schema) {
this.port = port;
this.schema = schema;
}

public Integer getPort() {
return port;
}

public String getSchema() {
return schema;
}
}

第一个需求是把Json串里的字符串解析为Enum

1
2
String portStr = "{\"ports\":[\"http\"]}";
objectMapper.readValue(portStr, DeployDTO.class)

如果没有意外的话,会报这个错误

Cannot deserialize value of type ProbePortEnum from String “http”: value not one of declared Enum instance names: [HTTP, GRPC]

要解决这个问题的话,@JsonValue就够了

1
2
3
4
@JsonValue
public String getSchema() {
return schema;
}

后来在调试的时候,觉得端口号是唯一的,直接用端口号比较好,就把返回的数据改成了下面这种

1
String portStr = "{\"ports\":[80]}";

@JsonValue只能添加在一个属性上面,这时就要把schema上的@JasonValue移到port上了

1
2
3
4
@JsonValue
public Integer getPort() {
return port;
}

结果却行不通了

Cannot deserialize value of type ProbePortEnum from number 80: index value outside legal index range [0..1]

原来是因为类型引起的——在 com.fasterxml.jackson.databind.deser.std.EnumDeserializer#deserialize可以看到有一个判断,如果是JsonToken.VALUE_NUMBER_INT就会通过Enum的索引来查找,而ProbePortEnum的索引只有0和1

解决这个问题有两种方式:

一种方式是把json串里的端口号改成字符串( 😳 具体原因可以去看下EnumDeserializer#deserialize)

1
String portStr = "{\"ports\":[\"80\"]}";

另一种是使用@JsonCreator

1
2
3
4
5
6
7
8
9
@JsonCreator
public static ProbePortEnum forValues(Integer port){
for(ProbePortEnum probePortEnum : ProbePortEnum.values()){
if (probePortEnum.port.equals(port)){
return probePortEnum;
}
}
return null;
}

前面一直在讨论是单个值的情况,如果想把两个字段都给反序列化出来

1
String portStr = "{\"ports\":[{\"port\":80,\"schema\":\"http\"}]}";

怎么办呢?只需要在@JsonCreator注解的方法上再添加上剩余的属性即可

1
2
3
4
5
6
7
8
9
@JsonCreator
public static ProbePortEnum forValues(@JsonProperty("port") Integer port, @JsonProperty("schema") String schema){
for(ProbePortEnum probePortEnum : ProbePortEnum.values()){
if (probePortEnum.port.equals(port) && probePortEnum.schema.equals(schema)){
return probePortEnum;
}
}
return null;
}

说完了反序列化,那么序列化呢?

1
2
3
DeployDTO deployDTO = DeployDTO.builder().ports(Arrays.asList(ProbePortEnum.HTTP)).build();
String json = objectMapper.writeValueAsString(deployDTO);
System.out.println(json);//{"ports":["HTTP"]}

默认输出的是Enum的字面量

如果只想输出某一个属性的话,还是用@JsonValue

如果想输出全部属性的话,可以换成@JsonFormat

1
2
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum ProbePortEnum

OVER~

据说打赏我的人,代码没有BUG