Comment désérialiser une chaîne JSON contenant des valeurs d'énumération insensibles à la casse? (en utilisant Jackson Databind)
La chaîne JSON:
[{"url": "foo", "type": "json"}]
et mon POJO Java:
public static class Endpoint {
public enum DataType {
JSON, HTML
}
public String url;
public DataType type;
public Endpoint() {
}
}
dans ce cas, la désérialisation du JSON avec "type":"json"
échouerait là où "type":"JSON"
fonctionnerait ..__ Mais je veux que "json"
fonctionne également pour les raisons de convention de nommage.
La sérialisation du POJO entraîne également la mise en majuscule "type":"JSON"
J'ai pensé à utiliser @JsonCreator
et @JsonGetter:
@JsonCreator
private Endpoint(@JsonProperty("name") String url, @JsonProperty("type") String type) {
this.url = url;
this.type = DataType.valueOf(type.toUpperCase());
}
//....
@JsonGetter
private String getType() {
return type.name().toLowerCase();
}
Et ça a fonctionné. Mais je me demandais s'il n'y avait pas une meilleure solution car cela me semble être un hack.
Je peux aussi écrire un désérialiseur personnalisé, mais de nombreux POJO différents utilisent des énumérations et ce serait difficile à maintenir.
Quelqu'un peut-il suggérer un meilleur moyen de sérialiser et de désérialiser les énumérations avec la convention de dénomination appropriée?
Je ne veux pas que mes enums en Java soient en minuscules!
Voici un code de test que j'ai utilisé:
String data = "[{\"url\":\"foo\", \"type\":\"json\"}]";
Endpoint[] arr = new ObjectMapper().readValue(data, Endpoint[].class);
System.out.println("POJO[]->" + Arrays.toString(arr));
System.out.println("JSON ->" + new ObjectMapper().writeValueAsString(arr));
Dans la version 2.4.0, vous pouvez enregistrer un sérialiseur personnalisé pour tous les types Enum ( link au problème github). Vous pouvez également remplacer vous-même le désérialiseur Enum standard, qui connaîtra le type Enum. Voici un exemple:
public class JacksonEnum {
public static enum DataType {
JSON, HTML
}
public static void main(String[] args) throws IOException {
List<DataType> types = Arrays.asList(JSON, HTML);
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.setDeserializerModifier(new BeanDeserializerModifier() {
@Override
public JsonDeserializer<Enum> modifyEnumDeserializer(DeserializationConfig config,
final JavaType type,
BeanDescription beanDesc,
final JsonDeserializer<?> deserializer) {
return new JsonDeserializer<Enum>() {
@Override
public Enum deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
Class<? extends Enum> rawClass = (Class<Enum<?>>) type.getRawClass();
return Enum.valueOf(rawClass, jp.getValueAsString().toUpperCase());
}
};
}
});
module.addSerializer(Enum.class, new StdSerializer<Enum>(Enum.class) {
@Override
public void serialize(Enum value, JsonGenerator jgen, SerializerProvider provider) throws IOException {
jgen.writeString(value.name().toLowerCase());
}
});
mapper.registerModule(module);
String json = mapper.writeValueAsString(types);
System.out.println(json);
List<DataType> types2 = mapper.readValue(json, new TypeReference<List<DataType>>() {});
System.out.println(types2);
}
}
Sortie:
["json","html"]
[JSON, HTML]
Jackson 2.9
C’est maintenant très simple, avec jackson-databind
2.9.0 et plus
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS);
// objectMapper now deserializes enums in a case-insensitive manner
Exemple complet avec des tests
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Main {
private enum TestEnum { ONE }
private static class TestObject { public TestEnum testEnum; }
public static void main (String[] args) {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS);
try {
TestObject uppercase =
objectMapper.readValue("{ \"testEnum\": \"ONE\" }", TestObject.class);
TestObject lowercase =
objectMapper.readValue("{ \"testEnum\": \"one\" }", TestObject.class);
TestObject mixedcase =
objectMapper.readValue("{ \"testEnum\": \"oNe\" }", TestObject.class);
if (uppercase.testEnum != TestEnum.ONE) throw new Exception("cannot deserialize uppercase value");
if (lowercase.testEnum != TestEnum.ONE) throw new Exception("cannot deserialize lowercase value");
if (mixedcase.testEnum != TestEnum.ONE) throw new Exception("cannot deserialize mixedcase value");
System.out.println("Success: all deserializations worked");
} catch (Exception e) {
e.printStackTrace();
}
}
}
J'ai rencontré le même problème dans mon projet, nous avons décidé de construire nos enums avec une clé de chaîne et d'utiliser @JsonValue
et un constructeur statique pour la sérialisation et la désérialisation, respectivement.
public enum DataType {
JSON("json"),
HTML("html");
private String key;
DataType(String key) {
this.key = key;
}
@JsonCreator
public static DataType fromString(String key) {
return key == null
? null
: DataType.valueOf(key.toUpperCase());
}
@JsonValue
public String getKey() {
return key;
}
}
Depuis Jackson 2.6, vous pouvez simplement faire ceci:
public enum DataType {
@JsonProperty("json")
JSON,
@JsonProperty("html")
HTML
}
Pour un exemple complet, voir this Gist .
Je suis allé pour la solution de Sam B. mais une variante plus simple.
public enum Type {
PIZZA, Apple, PEAR, SOUP;
@JsonCreator
public static Type fromString(String key) {
for(Type type : Type.values()) {
if(type.name().equalsIgnoreCase(key)) {
return type;
}
}
return null;
}
}
Si vous utilisez Spring Boot 2.1.x
avec Jackson 2.9
, vous pouvez simplement utiliser cette propriété de l'application:
spring.jackson.mapper.accept-case-insensitive-enums=true
J'ai utilisé une modification de la solution Iago Fernández et Paul.
J'ai eu un enum dans mon requestobject qui devait être insensible à la casse
@POST
public Response doSomePostAction(RequestObject object){
//resource implementation
}
class RequestObject{
//other params
MyEnumType myType;
@JsonSetter
public void setMyType(String type){
MyEnumType.valueOf(type.toUpperCase());
}
@JsonGetter
public String getType(){
return myType.toString();//this can change
}
}