与 Jackson 对枚举进行反序列化

我正在尝试用 Jackson 2.5.4反序列化一个枚举,但是失败了,我不太明白我的情况。我的输入字符串是驼峰大小写,我想简单地映射到标准的 Enum 约定。

@JsonFormat(shape = JsonFormat.Shape.STRING)
public enum Status {
READY("ready"),
NOT_READY("notReady"),
NOT_READY_AT_ALL("notReadyAtAll");


private static Map<String, Status> FORMAT_MAP = Stream
.of(Status.values())
.collect(toMap(s -> s.formatted, Function.<Status>identity()));


private final String formatted;


Status(String formatted) {
this.formatted = formatted;
}


@JsonCreator
public Status fromString(String string) {
Status status = FORMAT_MAP.get(string);
if (status == null) {
throw new IllegalArgumentException(string + " has no corresponding value");
}
return status;
}
}

我还在一个 getter 上尝试了 @JsonValue,但是没有用,这是我在其他地方看到的一个选项。他们都爆炸了:

com.fasterxml.jackson.databind.exc.InvalidFormatException: Can not construct instance of ...Status from String value 'ready': value not one of declared Enum instance names: ...

我做错了什么?

129285 次浏览

编辑: 从 Jackson 2.6开始,可以在枚举的每个元素上使用 @JsonProperty来指定其序列化/反序列化值(看这里) :

public enum Status {
@JsonProperty("ready")
READY,
@JsonProperty("notReady")
NOT_READY,
@JsonProperty("notReadyAtAll")
NOT_READY_AT_ALL;
}

(这个答案的其余部分仍然适用于老版本的杰克逊)

您应该使用 @JsonCreator来注释接收 String参数的静态方法:

public enum Status {
READY("ready"),
NOT_READY("notReady"),
NOT_READY_AT_ALL("notReadyAtAll");


private static Map<String, Status> FORMAT_MAP = Stream
.of(Status.values())
.collect(Collectors.toMap(s -> s.formatted, Function.identity()));


private final String formatted;


Status(String formatted) {
this.formatted = formatted;
}


@JsonCreator // This is the factory method and must be static
public static Status fromString(String string) {
return Optional
.ofNullable(FORMAT_MAP.get(string))
.orElseThrow(() -> new IllegalArgumentException(string));
}
}

这是个测试:

ObjectMapper mapper = new ObjectMapper();


Status s1 = mapper.readValue("\"ready\"", Status.class);
Status s2 = mapper.readValue("\"notReadyAtAll\"", Status.class);


System.out.println(s1); // READY
System.out.println(s2); // NOT_READY_AT_ALL

由于工厂方法需要 String,因此必须对字符串使用 JSON 有效语法,即引用值。

这可能是一种更快捷的方式:

public enum Status {
READY("ready"),
NOT_READY("notReady"),
NOT_READY_AT_ALL("notReadyAtAll");


private final String formatted;


Status(String formatted) {
this.formatted = formatted;
}


@Override
public String toString() {
return formatted;
}
}


public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
ObjectReader reader = mapper.reader(Status.class);
Status status = reader.with(DeserializationFeature.READ_ENUMS_USING_TO_STRING).readValue("\"notReady\"");
System.out.println(status.name());  // NOT_READY
}

此页面上的解决方案仅适用于单个字段和@JsonFormat (form = JsonFormat. Shape.NATURAL)(默认格式)

这适用于多个字段和@JsonFormat (form = JsonFormat. Shape.OBJECT)

@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum PinOperationMode {
INPUT("Input", "I"),
OUTPUT("Output", "O")
;


private final String mode;
private final String code;


PinOperationMode(String mode, String code) {
this.mode = mode;
this.code = code;
}


public String getMode() {
return mode;
}


public String getCode() {
return code;
}


@JsonCreator
static PinOperationMode findValue(@JsonProperty("mode") String mode, @JsonProperty("code") String code) {
return Arrays.stream(PinOperationMode.values()).filter(pt -> pt.mode.equals(mode) && pt.code.equals(code)).findFirst().get();
}
}

对于那些正在搜索具有整数 json 属性的枚举的人来说,下面是对我有效的方法:

enum class Status (private val code: Int) {
PAST(0),
LIVE(2),
UPCOMING(1);
companion object {
private val codes = Status.values().associateBy(Status::code)
@JvmStatic @JsonCreator fun from (value: Int) = codes[value]
}
}
@JsonCreator
public static Status forValue(String name)
{
return EnumUtil.getEnumByNameIgnoreCase(Status.class, name);
}

添加此静态方法将解决反序列化的问题

您可以使用 @JsonCreator注释来解决您的问题。看一下 https://www.baeldung.com/jackson-serialize-enums,在 Jackson lib 中有关于枚举和序列化-反序列化的足够清楚的解释。

@JsonCreator(mode = JsonCreator.Mode.DELEGATING)是我的解决方案。

Https://github.com/fasterxml/jackson-module-kotlin/issues/336#issuecomment-630587525