I would like to decode lowercase and uppercase values for a Java Enum in both plain values and Array.
I have tried this piece of code:
public enum FooBarEnum {
FOO,
BAR
}
the following POJOs to deserialize things:
public record Aaa(String something, FooBarEnum foobar)
public record Bbb(String something, List<FooBarEnum> foobarList)
When trying to get the object via HTTP request:
@GetExchange("/api/test1")
Aaa getAaa(@PathVariable int key);
For this, it works, no matter if the server responds to an object with FOO, foo, BAR, or bar, it works.
However, I have this:
@GetExchange("/api/test2")
Bbb getBbb(@PathVariable int key);
When it is in lower case, I will encounter this exception:
JSON decoding error: Cannot deserialize value of type `com.FooBarEnum` from String "foo": not one of the values accepted for Enum class: [FOO, BAR]
[...]
Caused by: com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `com.FooBarEnum` from String "foo": not one of the values accepted for Enum class: [FOO, BAR]
at [Source: REDACTED (`StreamReadFeature.INCLUDE_SOURCE_IN_LOCATION` disabled); line: 1, column: 996] (through reference chain: com.Bbb["fooBarList"]-java.util.ArrayList[0])
at com.fasterxml.jackson.databind.exc.InvalidFormatException.from(InvalidFormatException.java:67) ~[jackson-databind-2.18.3.jar:2.18.3]
I even tried to add
@JsonFormat(with = {JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_VALUES})
but no luck.
Is there a way to handle the ignore case for array as well?
no matter if the server responds to an object with FOO, foo, BAR, or bar, it works!
This is an odd behavior. By default, Jackson serializes and deserializes enum values with the name of the corresponding constant. This is why FOO
and BAR
are the only values produced during serialization, and the only ones accepted at deserialization. Here is an example to showcase the behavior. In your case, there must be a missing configuration that makes the endpoint getAaa()
work. Instead, getBbb()
behaves as expected.
As you've already mentioned, the annotation @JsonFormat
along with the features ACCEPT_CASE_INSENSITIVE_PROPERTIES
and ACCEPT_CASE_INSENSITIVE_VALUES
would deserialize regardless of the case. However, it must annotate the enum fields. I don't know if perhaps you used a class instead of a record and annotated something else. It's not clear how you used the annotation from the question's post. Here is a demo with the desired behavior.
record Aaa(String something,
@JsonFormat(with = {JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_VALUES}) FooBarEnum foo) {
}
record Bbb(String something,
@JsonFormat(with = {JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, JsonFormat.Feature.ACCEPT_CASE_INSENSITIVE_VALUES}) List<FooBarEnum> foo) {
}
public class Main {
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
Aaa aaa = new Aaa("test", FooBarEnum.FOO);
String json = mapper.writeValueAsString(aaa);
System.out.println(json);
json = json.replace("FOO", "foo");
System.out.println(json);
aaa = mapper.readValue(json, Aaa.class);
System.out.println(aaa);
Bbb bbb = new Bbb("test", List.of(FooBarEnum.FOO, FooBarEnum.BAR));
json = mapper.writeValueAsString(bbb);
System.out.println(json);
json = json.replace("FOO", "foo").replace("BAR", "bar");
System.out.println(json);
bbb = mapper.readValue(json, Bbb.class);
System.out.println(bbb);
}
}
Another alternative is to define a custom deserializer and register it with a module on the ObjectMapper
. Here is another demo.
class FooBarDeserializer extends StdDeserializer<FooBarEnum> {
public FooBarDeserializer() {
this(null);
}
public FooBarDeserializer(Class<FooBarEnum> clazz) {
super(clazz);
}
@Override
public FooBarEnum deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JacksonException {
JsonNode node = p.getCodec().readTree(p);
return Arrays.stream(FooBarEnum.values())
.filter(e -> e.name().equalsIgnoreCase(node.asText()))
.findFirst()
.orElseThrow(() -> new RuntimeException("No enum found for value " + node.asText()));
}
}
public class Main {
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(FooBarEnum.class, new FooBarDeserializer());
mapper.registerModule(module);
Aaa aaa = new Aaa("test", FooBarEnum.FOO);
String json = mapper.writeValueAsString(aaa);
System.out.println(json);
json = json.replace("FOO", "foo");
System.out.println(json);
aaa = mapper.readValue(json, Aaa.class);
System.out.println(aaa);
Bbb bbb = new Bbb("test", List.of(FooBarEnum.FOO, FooBarEnum.BAR));
json = mapper.writeValueAsString(bbb);
System.out.println(json);
json = json.replace("FOO", "foo").replace("BAR", "bar");
System.out.println(json);
bbb = mapper.readValue(json, Bbb.class);
System.out.println(bbb);
}
}