Jackson databind enum không phân biệt chữ hoa chữ thường


99

Làm cách nào để giải mã chuỗi JSON chứa các giá trị enum không phân biệt chữ hoa chữ thường? (sử dụng Jackson Databind)

Chuỗi JSON:

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

và Java POJO của tôi:

public static class Endpoint {

    public enum DataType {
        JSON, HTML
    }

    public String url;
    public DataType type;

    public Endpoint() {

    }

}

trong trường hợp này, giải mã hóa JSON với "type":"json"sẽ không hoạt động nếu như "type":"JSON"sẽ hoạt động. Nhưng tôi cũng muốn "json"làm việc vì lý do quy ước đặt tên.

Việc tuần tự hóa POJO cũng dẫn đến trường hợp viết hoa "type":"JSON"

Tôi đã nghĩ đến việc sử dụng @JsonCreatorvà @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();
    }

Va no đa hoạt động. Nhưng tôi đã tự hỏi liệu có một giải pháp tốt hơn bởi vì điều này trông giống như một hack đối với tôi.

Tôi cũng có thể viết một trình giải mã tùy chỉnh nhưng tôi có nhiều POJO khác nhau sử dụng enum và sẽ khó bảo trì.

Bất cứ ai có thể đề xuất một cách tốt hơn để tuần tự hóa và giải mã hóa các enums với quy ước đặt tên thích hợp?

Tôi không muốn enum của tôi trong java là chữ thường!

Đây là một số mã thử nghiệm mà tôi đã sử dụng:

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

Bạn đang ở phiên bản nào của Jackson? Hãy xem JIRA jira.codehaus.org/browse/JACKSON-861 này
Alexey Gavrilov.

Tôi đang sử dụng Jackson 2.2.3
tom91136

OK tôi chỉ cập nhật để 2.4.0-RC3
tom91136

Câu trả lời:


38

Trong phiên bản 2.4.0, bạn có thể đăng ký một bộ nối tiếp tùy chỉnh cho tất cả các loại Enum ( liên kết đến vấn đề github). Ngoài ra, bạn có thể tự mình thay thế bộ khử không khí Enum tiêu chuẩn sẽ biết về loại Enum. Đây là một ví dụ:

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

Đầu ra:

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

1
Cảm ơn, bây giờ tôi có thể loại bỏ tất cả các soạn sẵn trong POJO của tôi :)
tom91136

Cá nhân tôi đang ủng hộ điều này trong các dự án của mình. Nếu bạn nhìn vào ví dụ của tôi, nó yêu cầu rất nhiều mã soạn sẵn. Một lợi ích của việc sử dụng các thuộc tính riêng biệt cho de / serialization là nó tách tên của các giá trị quan trọng trong Java (tên enum) thành các giá trị quan trọng của máy khách (bản in đẹp). Ví dụ: nếu bạn muốn thay đổi Kiểu dữ liệu HTML thành HTML_DATA_TYPE, bạn có thể làm như vậy mà không ảnh hưởng đến API bên ngoài, nếu có khóa được chỉ định.
Sam Berry

1
Đây là một khởi đầu tốt nhưng nó sẽ thất bại nếu enum của bạn đang sử dụng JsonProperty hoặc JsonCreator. Dropwizard có FuzzyEnumModule là một triển khai mạnh mẽ hơn.
Pixel Elephant

134

Jackson 2,9

Điều này hiện rất đơn giản, sử dụng jackson-databind2.9.0 trở lên

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

// objectMapper now deserializes enums in a case-insensitive manner

Ví dụ đầy đủ với các bài kiểm tra

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

3
Cái này là vàng!
Vikas Prasad

8
Tôi đang sử dụng 2.9.2 và nó không hoạt động. Gây ra bởi: com.fasterxml.jackson.databind.exc.InvalidFormatException: Không thể deserialize giá trị của loại .... Gender` từ Chuỗi "male": giá trị không phải là một trong các tên phiên bản Enum đã khai báo: [FAMALE, MALE]
Jordan Silva

@JordanSilva nó chắc chắn hoạt động với v2.9.2. Tôi đã thêm một ví dụ mã đầy đủ với các thử nghiệm để xác minh. Tôi không biết điều gì có thể đã xảy ra trong trường hợp của bạn, nhưng chạy mã ví dụ jackson-databindcụ thể với 2.9.2 hoạt động như mong đợi.
davnicwil

5
sử dụng Spring Boot, bạn chỉ có thể thêm tài sảnspring.jackson.mapper.accept-case-insensitive-enums=true
Arne Burmeister

1
@JordanSilva có lẽ bạn đang cố gắng deserialize enum để lấy các tham số như tôi đã làm? =) Tôi đã giải quyết vấn đề của mình và trả lời ở đây. Hy vọng nó có thể giúp
Konstantin Zyubin

83

Tôi đã gặp phải vấn đề tương tự này trong dự án của mình, chúng tôi quyết định xây dựng các enums của chúng tôi với một khóa chuỗi và sử dụng @JsonValuevà một hàm tạo tĩnh để tuần tự hóa và deserialization tương ứng.

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
Điều này nên DataType.valueOf(key.toUpperCase())- nếu không, bạn đã không thực sự thay đổi bất cứ điều gì. Mã hóa phòng thủ để tránh NPE:return (null == key ? null : DataType.valueOf(key.toUpperCase()))
sarumont

2
Bắt tốt @sarumont. Tôi đã thực hiện chỉnh sửa. Ngoài ra, đổi tên phương thức thành "fromString" để chơi tốt với JAX-RS .
Sam Berry

1
Tôi thích cách tiếp cận này, nhưng đã chọn một biến thể ít dài dòng hơn, xem bên dưới.
linqu

2
rõ ràng keylĩnh vực này là không cần thiết. Trong getKey, bạn có thể chỉreturn name().toLowerCase()
yair

1
Tôi thích trường khóa trong trường hợp bạn muốn đặt tên cho enum khác với tên json sẽ có. Trong trường hợp của tôi, một hệ thống kế thừa gửi một tên thực sự viết tắt và khó nhớ cho giá trị mà nó gửi, và tôi có thể sử dụng trường này để dịch sang một tên tốt hơn cho java enum của tôi.
grinch

47

Kể từ Jackson 2.6, bạn chỉ cần thực hiện điều này:

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

Để có ví dụ đầy đủ, hãy xem ý chính này .


26
Lưu ý rằng làm điều này sẽ đảo ngược vấn đề. Bây giờ Jackson sẽ chỉ chấp nhận chữ thường và từ chối mọi giá trị viết hoa hoặc viết hoa hỗn hợp.
Pixel Elephant,

30

Tôi đã tìm giải pháp của Sam B. nhưng một biến thể đơn giản hơn.

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

Tôi không nghĩ điều này đơn giản hơn. DataType.valueOf(key.toUpperCase())là một khởi tạo trực tiếp nơi bạn có một vòng lặp. Đây có thể là một vấn đề đối với rất nhiều enum. Tất nhiên, valueOfcó thể ném IllegalArgumentException mà mã của bạn tránh, vì vậy đó là một lợi ích tốt nếu bạn thích kiểm tra null hơn kiểm tra ngoại lệ.
Patrick M

22

Nếu bạn đang sử dụng Spring Boot 2.1.xvới Jackson, 2.9bạn chỉ cần sử dụng thuộc tính ứng dụng này:

spring.jackson.mapper.accept-case-insensitive-enums=true


3

Đối với những người cố gắng deserialize Enum bỏ qua trường hợp trong tham số GET , việc bật ACCEPT_CASE_INSENSITIVE_ENUMS sẽ không có tác dụng gì. Nó sẽ không hữu ích bởi vì tùy chọn này chỉ hoạt động cho cơ thể deserialization . Thay vào đó hãy thử điều này:

public class StringToEnumConverter implements Converter<String, Modes> {
    @Override
    public Modes convert(String from) {
        return Modes.valueOf(from.toUpperCase());
    }
}

và sau đó

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new StringToEnumConverter());
    }
}

Câu trả lời và các mẫu mã là từ đây


1

Với lời xin lỗi đến @Konstantin Zyubin, câu trả lời của anh ấy gần với những gì tôi cần - nhưng tôi không hiểu nó, vì vậy đây là cách tôi nghĩ nó sẽ diễn ra:

Nếu bạn muốn deserialize một kiểu enum dưới dạng không phân biệt chữ hoa chữ thường - tức là bạn không muốn hoặc không thể sửa đổi hành vi của toàn bộ ứng dụng, bạn có thể tạo một deserializer tùy chỉnh chỉ cho một loại - bằng cách phân loại phụ StdConvertervà buộc Jackson chỉ sử dụng nó trên các trường liên quan bằng cách sử dụng JsonDeserializechú thích.

Thí dụ:

public class ColorHolder {

  public enum Color {
    RED, GREEN, BLUE
  }

  public static final class ColorParser extends StdConverter<String, Color> {
    @Override
    public Color convert(String value) {
      return Arrays.stream(Color.values())
        .filter(e -> e.getName().equalsIgnoreCase(value.trim()))
        .findFirst()
        .orElseThrow(() -> new IllegalArgumentException("Invalid value '" + value + "'"));
    }
  }

  @JsonDeserialize(converter = ColorParser.class)
  Color color;
}

0

Sự cố được chuyển xuống com.fasterxml.jackson.databind.util.EnumResolver . nó sử dụng HashMap để giữ các giá trị enum và HashMap không hỗ trợ các khóa phân biệt chữ hoa chữ thường.

trong các câu trả lời ở trên, tất cả các ký tự phải là chữ hoa hoặc chữ thường. nhưng tôi đã khắc phục tất cả (trong) các vấn đề nhạy cảm đối với enums với điều đó:

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

Sử dụng:

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

}

0

Tôi đã sử dụng một sửa đổi của giải pháp Iago Fernández và Paul.

Tôi có một enum trong đối tượng yêu cầu của mình mà cần phải phân biệt chữ hoa chữ thường

@POST
public Response doSomePostAction(RequestObject object){
 //resource implementation
}



class RequestObject{
 //other params 
 MyEnumType myType;

 @JsonSetter
 public void setMyType(String type){
   myType = MyEnumType.valueOf(type.toUpperCase());
 }
 @JsonGetter
 public String getType(){
   return myType.toString();//this can change 
 }
}

0

Để cho phép không phân biệt chữ hoa chữ thường của enums trong jackson, chỉ cần thêm thuộc tính dưới đây vào application.propertiestệp của dự án khởi động mùa xuân của bạn.

spring.jackson.mapper.accept-case-insensitive-enums=true

Nếu bạn có phiên bản yaml của tệp thuộc tính, hãy thêm thuộc tính bên dưới vào application.ymltệp của bạn .

spring:
  jackson:
    mapper:
      accept-case-insensitive-enums: true

-1

Dưới đây là cách tôi đôi khi xử lý enums khi tôi muốn deserialize theo cách không phân biệt chữ hoa chữ thường (xây dựng trên mã được đăng trong câu hỏi):

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

Nếu enum không tầm thường và do đó trong lớp riêng của nó, tôi thường thêm một phương thức phân tích cú pháp tĩnh để xử lý các Chuỗi viết thường.


-1

Deserialize enum với jackson rất đơn giản. Khi bạn muốn deserialize enum dựa trên String cần một hàm tạo, một getter và một setter cho enum của bạn.

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

}

Khi bạn có json của mình, bạn có thể deserialize đến lớp Endpoint bằng ObjectMapper của Jackson:

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();
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.