首页 文章

Jackson databind enum不区分大小写

提问于
浏览
59

如何反序列化包含不区分大小写的枚举值的JSON字符串? (使用Jackson Databind)

JSON字符串:

[{"url": "foo", "type": "json"}]

和我的Java POJO:

public static class Endpoint {

    public enum DataType {
        JSON, HTML
    }

    public String url;
    public DataType type;

    public Endpoint() {

    }

}

在这种情况下,使用 "type":"json" 反序列化JSON将失败,因为 "type":"JSON" 可以工作 . 但我希望 "json" 能够用于命名约定的原因 .

序列化POJO也会导致大写 "type":"JSON"

我想过使用 @JsonCreator 和@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();
    }

它奏效了 . 但我想知道是否有更好的解决方案,因为这看起来像是对我的黑客攻击 .

我也可以编写一个自定义反序列化器,但是我有很多不同的POJO使用枚举,而且很难维护 .

任何人都可以建议一个更好的方法来使用适当的命名约定序列化和反序列化枚举?

I don't want my enums in java to be lowercase!

这是我使用的一些测试代码:

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

9 回答

  • 68

    在2.4.0版中,您可以为所有Enum类型注册自定义序列化程序(link到github问题) . 此外,您可以自行更换标准的Enum反序列化器,以便了解Enum类型 . 这是一个例子:

    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);
        }
    }
    

    输出:

    ["json","html"]
    [JSON, HTML]
    
  • 20

    Jackson 2.9

    现在这很简单,从 jackson-databind 2.9.0及以上

    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS);
    ...
    

    完整的代码示例

    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();
        }
      }
    }
    
  • 64

    我在我的项目中遇到了同样的问题,我们决定使用字符串键构建枚举,并分别使用 @JsonValue 和静态构造函数进行序列化和反序列化 .

    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;
        }
    }
    
  • -1

    从Jackson 2.6开始,您可以简单地执行此操作:

    public enum DataType {
            @JsonProperty("json")
            JSON,
            @JsonProperty("html")
            HTML
        }
    

    有关完整示例,请参阅this gist .

  • 0

    我选择了Sam B.的解决方案,但这是一个更简单的变体 .

    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;
        }
    }
    
  • 31

    我使用了IagoFernández和Paul解决方案的修改 .

    我的requestobject中有一个枚举,需要不区分大小写

    @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 
     }
    }
    
  • 26

    问题归结为com.fasterxml.jackson.databind.util.EnumResolver . 它使用HashMap来保存枚举值,而HashMap不支持不区分大小写的键 .

    在上面的答案中,所有字符都应该是大写或小写 . 但我修复了枚举的所有敏感问题:

    https://gist.github.com/bhdrk/02307ba8066d26fa1537

    CustomDeserializers.java

    import com.fasterxml.jackson.databind.BeanDescription;
    import com.fasterxml.jackson.databind.DeserializationConfig;
    import com.fasterxml.jackson.databind.JsonDeserializer;
    import com.fasterxml.jackson.databind.JsonMappingException;
    import com.fasterxml.jackson.databind.deser.std.EnumDeserializer;
    import com.fasterxml.jackson.databind.module.SimpleDeserializers;
    import com.fasterxml.jackson.databind.util.EnumResolver;
    
    import java.util.HashMap;
    import java.util.Map;
    
    
    public class CustomDeserializers extends SimpleDeserializers {
    
        @Override
        @SuppressWarnings("unchecked")
        public JsonDeserializer<?> findEnumDeserializer(Class<?> type, DeserializationConfig config, BeanDescription beanDesc) throws JsonMappingException {
            return createDeserializer((Class<Enum>) type);
        }
    
        private <T extends Enum<T>> JsonDeserializer<?> createDeserializer(Class<T> enumCls) {
            T[] enumValues = enumCls.getEnumConstants();
            HashMap<String, T> map = createEnumValuesMap(enumValues);
            return new EnumDeserializer(new EnumCaseInsensitiveResolver<T>(enumCls, enumValues, map));
        }
    
        private <T extends Enum<T>> HashMap<String, T> createEnumValuesMap(T[] enumValues) {
            HashMap<String, T> map = new HashMap<String, T>();
            // from last to first, so that in case of duplicate values, first wins
            for (int i = enumValues.length; --i >= 0; ) {
                T e = enumValues[i];
                map.put(e.toString(), e);
            }
            return map;
        }
    
        public static class EnumCaseInsensitiveResolver<T extends Enum<T>> extends EnumResolver<T> {
            protected EnumCaseInsensitiveResolver(Class<T> enumClass, T[] enums, HashMap<String, T> map) {
                super(enumClass, enums, map);
            }
    
            @Override
            public T findEnum(String key) {
                for (Map.Entry<String, T> entry : _enumsById.entrySet()) {
                    if (entry.getKey().equalsIgnoreCase(key)) { // magic line <--
                        return entry.getValue();
                    }
                }
                return null;
            }
        }
    }
    

    Usage:

    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.module.SimpleModule;
    
    
    public class JSON {
    
        public static void main(String[] args) {
            SimpleModule enumModule = new SimpleModule();
            enumModule.setDeserializers(new CustomDeserializers());
    
            ObjectMapper mapper = new ObjectMapper();
            mapper.registerModule(enumModule);
        }
    
    }
    
  • -1

    当我想以不区分大小写的方式反序列化时(基于问题中发布的代码),我有时会处理枚举的方式:

    @JsonIgnore
    public void setDataType(DataType dataType)
    {
      type = dataType;
    }
    
    @JsonProperty
    public void setDataType(String dataType)
    {
      // Clean up/validate String however you want. I like
      // org.apache.commons.lang3.StringUtils.trimToEmpty
      String d = StringUtils.trimToEmpty(dataType).toUpperCase();
      setDataType(DataType.valueOf(d));
    }
    

    如果枚举是非平凡的,因此在它自己的类中,我通常会添加一个静态解析方法来处理小写字符串 .

  • -1

    用 Jackson 反序列化枚举很简单 . 当您想要基于String的反序列化枚举需要一个构造函数,一个getter和一个使用该枚举的enum.Also类的setter必须有一个setter接收DataType作为param,而不是String:

    public class Endpoint {
    
         public enum DataType {
            JSON("json"), HTML("html");
    
            private String type;
    
            @JsonValue
            public String getDataType(){
               return type;
            }
    
            @JsonSetter
            public void setDataType(String t){
               type = t.toLowerCase();
            }
         }
    
         public String url;
         public DataType type;
    
         public Endpoint() {
    
         }
    
         public void setType(DataType dataType){
            type = dataType;
         }
    
    }
    

    当你有了json时,你可以使用Jackson的ObjectMapper反序列化为Endpoint类:

    ObjectMapper mapper = new ObjectMapper();
    mapper.enable(SerializationFeature.INDENT_OUTPUT);
    try {
        Endpoint endpoint = mapper.readValue("{\"url\":\"foo\",\"type\":\"json\"}", Endpoint.class);
    } catch (IOException e1) {
            // TODO Auto-generated catch block
        e1.printStackTrace();
    }
    

相关问题