web-dev-qa-db-fra.com

Jackson databind enum insensible à la casse

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));
65
tom91136

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]
32
Alexey Gavrilov

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();
    }
  }
}
75
davnicwil

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;
    }
}
67
Sam Berry

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 .

29
Andrew Bickerton

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;
    }
}
25
linqu

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

1
etSingh

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 
 }
}
0
trooper31