Jackson databind enum ไม่คำนึงถึงตัวพิมพ์เล็กและใหญ่


108

ฉันจะ deserialize สตริง JSON ที่มีค่า enum ที่ไม่คำนึงถึงตัวพิมพ์เล็กและใหญ่ได้อย่างไร (โดยใช้ 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() {

    }

}

ในกรณีนี้การ deserializing JSON ด้วย"type":"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();
    }

และมันได้ผล แต่ฉันสงสัยว่ามีโซลูทูออนที่ดีกว่าหรือไม่เพราะมันดูเหมือนแฮ็คสำหรับฉัน

ฉันยังสามารถเขียน deserializer ที่กำหนดเองได้ แต่ฉันมี POJO ที่แตกต่างกันมากมายที่ใช้ enums และมันยากที่จะดูแลรักษา

มีใครพอจะแนะนำวิธีที่ดีกว่าในการทำให้เป็นอนุกรมและ deserialize enums ด้วยหลักการตั้งชื่อที่เหมาะสมได้ไหม

ฉันไม่ต้องการให้ enums ของฉันใน java เป็นตัวพิมพ์เล็ก!

นี่คือรหัสทดสอบบางส่วนที่ฉันใช้:

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

คุณเป็นแจ็คสันเวอร์ชั่นไหน? ดู JIRA jira.codehaus.org/browse/JACKSON-861
Alexey Gavrilov

ฉันใช้ Jackson 2.2.3
tom91136

ตกลงฉันเพิ่งอัปเดตเป็น 2.4.0-RC3
tom91136

คำตอบ:


38

ในเวอร์ชัน 2.4.0 คุณสามารถลงทะเบียนซีเรียลไลเซอร์แบบกำหนดเองสำหรับ Enum ทุกประเภท ( ลิงก์ไปยังปัญหา github) นอกจากนี้คุณสามารถเปลี่ยน Enum deserializer มาตรฐานได้ด้วยตัวคุณเองซึ่งจะต้องระวังเกี่ยวกับประเภท 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]

1
ขอบคุณตอนนี้ฉันสามารถลบต้นแบบทั้งหมดใน POJO ของฉันได้แล้ว :)
tom91136

ฉันเองกำลังสนับสนุนสิ่งนี้ในโครงการของฉัน หากคุณดูตัวอย่างของฉันต้องใช้รหัสสำเร็จรูปจำนวนมาก ข้อดีอย่างหนึ่งของการใช้แอตทริบิวต์แยกต่างหากสำหรับการ de / serialization คือการแยกชื่อของค่าที่สำคัญของ Java (ชื่อ enum) เป็นค่าที่สำคัญของไคลเอ็นต์ (ค่อนข้างพิมพ์) เช่นหากต้องการเปลี่ยน HTML DataType เป็น HTML_DATA_TYPE คุณสามารถทำได้โดยไม่ส่งผลกระทบต่อ API ภายนอกหากมีการระบุคีย์
Sam Berry

1
นี่เป็นการเริ่มต้นที่ดี แต่จะล้มเหลวหาก enum ของคุณใช้ JsonProperty หรือ JsonCreator Dropwizard มีFuzzyEnumModuleซึ่งเป็นการใช้งานที่มีประสิทธิภาพมากขึ้น
Pixel Elephant

148

แจ็คสัน 2.9

ตอนนี้ง่ายมากโดยใช้jackson-databind2.9.0 ขึ้นไป

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

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

ตัวอย่างเต็มพร้อมการทดสอบ

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
อันนี้ทอง!
Vikas Prasad

9
ฉันใช้ 2.9.2 และใช้งานไม่ได้ เกิดจาก: com.fasterxml.jackson.databind.exc.InvalidFormatException: ไม่สามารถ deserialize ค่าประเภท .... Gender` จาก String "male": ค่าที่ไม่ใช่หนึ่งในชื่ออินสแตนซ์ Enum ที่ประกาศ: [FAMALE, MALE]
Jordan Silva

@JordanSilva มันใช้งานได้กับ v2.9.2 อย่างแน่นอน ฉันได้เพิ่มตัวอย่างโค้ดแบบเต็มพร้อมการทดสอบเพื่อการตรวจสอบ ฉันไม่รู้ว่าอาจเกิดอะไรขึ้นในกรณีของคุณ แต่การเรียกใช้โค้ดตัวอย่างด้วยjackson-databind2.9.2 ทำงานได้ตามที่คาดไว้
davnicwil

8
โดยใช้ Spring Boot คุณสามารถเพิ่มคุณสมบัติspring.jackson.mapper.accept-case-insensitive-enums=true
Arne Burmeister

1
@JordanSilva บางทีคุณอาจพยายาม deserialize enum เพื่อรับพารามิเตอร์เหมือนที่ฉันทำ =) ฉันได้แก้ไขปัญหาของฉันและตอบที่นี่แล้ว หวังว่ามันจะช่วยได้นะ
Konstantin Zyubin

87

ฉันพบปัญหาเดียวกันนี้ในโครงการของฉันเราตัดสินใจที่จะสร้าง enums ของเราด้วยคีย์สตริงและการใช้งาน@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
สิ่งนี้ควรจะเป็นDataType.valueOf(key.toUpperCase())- มิฉะนั้นคุณไม่ได้เปลี่ยนแปลงอะไรเลย การเข้ารหัสเพื่อป้องกัน NPE:return (null == key ? null : DataType.valueOf(key.toUpperCase()))
sarumont

2
จับดี @sarumont. ฉันได้ทำการแก้ไขแล้ว นอกจากนี้วิธีการเปลี่ยนชื่อเป็น "fromString" เพื่อเล่นอย่างกับ JAX-RS
Sam Berry

1
ฉันชอบวิธีนี้ แต่เลือกใช้ตัวแปรที่ละเอียดน้อยกว่าดูด้านล่าง
linqu

2
เห็นได้ชัดว่าkeyฟิลด์นั้นไม่จำเป็น ในgetKeyคุณก็ทำได้return name().toLowerCase()
yair

1
ฉันชอบฟิลด์คีย์ในกรณีที่คุณต้องการตั้งชื่อ enum ให้แตกต่างจากสิ่งที่ json จะมี ในกรณีของฉันระบบเดิมส่งชื่อที่ย่อและจำยากสำหรับค่าที่ส่งมาและฉันสามารถใช้ฟิลด์นี้เพื่อแปลเป็นชื่อที่ดีกว่าสำหรับ java enum ของฉัน
grinch

55

ตั้งแต่ Jackson 2.6 คุณสามารถทำได้ง่ายๆ:

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

ดูตัวอย่างทั้งหมดได้ที่ส่วนสำคัญนี้


29
โปรดทราบว่าการทำเช่นนี้จะย้อนกลับปัญหา ตอนนี้ Jackson จะยอมรับเฉพาะตัวพิมพ์เล็กและปฏิเสธค่าตัวพิมพ์ใหญ่หรือตัวพิมพ์เล็กทั้งหมด
Pixel Elephant

30

ฉันไปหาวิธีแก้ปัญหาของ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;
    }
}

ฉันไม่คิดว่ามันจะง่ายกว่านี้ DataType.valueOf(key.toUpperCase())เป็นอินสแตนซ์โดยตรงที่คุณมีลูป นี่อาจเป็นปัญหาสำหรับ enum จำนวนมาก แน่นอนคุณvalueOfสามารถโยน IllegalArgumentException ซึ่งรหัสของคุณหลีกเลี่ยงได้ดังนั้นจึงเป็นประโยชน์ที่ดีหากคุณต้องการการตรวจสอบค่าว่างในการตรวจสอบข้อยกเว้น
Patrick M

25

หากคุณใช้ Spring Boot 2.1.xกับ Jackson 2.9คุณสามารถใช้คุณสมบัติของแอปพลิเคชันนี้:

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


4

สำหรับผู้ที่พยายามยกเลิกการกำหนดค่าเริ่มต้นของ Enum โดยไม่สนใจกรณีในพารามิเตอร์ GET การเปิดใช้งาน ACCEPT_CASE_INSENSITIVE_ENUMS จะไม่เป็นผลดีใด ๆ จะไม่ช่วยเพราะตัวเลือกนี้ใช้ได้กับการแยกส่วนของร่างกายเท่านั้น ลองสิ่งนี้แทน:

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

แล้ว

@Configuration
public class WebConfig implements WebMvcConfigurer {

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

ตัวอย่างคำตอบและโค้ดมาจากที่นี่


1

ด้วยการขอโทษ @Konstantin Zyubin คำตอบของเขาใกล้เคียงกับสิ่งที่ฉันต้องการ - แต่ฉันไม่เข้าใจดังนั้นนี่คือวิธีที่ฉันคิดว่าควรไป:

หากคุณต้องการ deserialize enum ประเภทหนึ่งโดยไม่คำนึงถึงตัวพิมพ์เล็ก - ใหญ่กล่าวคือคุณไม่ต้องการหรือไม่สามารถแก้ไขลักษณะการทำงานของแอปพลิเคชันทั้งหมดได้คุณสามารถสร้าง deserializer ที่กำหนดเองสำหรับประเภทเดียวโดยการแบ่งประเภทย่อยStdConverterและบังคับ Jackson ใช้เฉพาะในฟิลด์ที่เกี่ยวข้องโดยใช้ไฟล์JsonDeserializeคำอธิบายประกอบ

ตัวอย่าง:

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

1

หากต้องการอนุญาตให้ deserialization enums ในแจ็คสันแบบไม่คำนึงถึงตัวพิมพ์เล็กและใหญ่เพียงเพิ่มคุณสมบัติด้านล่างลงในapplication.propertiesไฟล์ของโครงการสปริงบูตของคุณ

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

หากคุณมีไฟล์คุณสมบัติเวอร์ชัน yaml ให้เพิ่มคุณสมบัติด้านล่างลงในapplication.ymlไฟล์ของคุณ

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

0

ปัญหาเกี่ยวข้องกับcom.fasterxml.jackson.databind.util.EnumResolver com.fasterxml.jackson.databind.util.EnumResolverใช้ HashMap เพื่อเก็บค่า enum และ HashMap ไม่รองรับคีย์ที่ไม่คำนึงถึงตัวพิมพ์เล็กและใหญ่

ในคำตอบด้านบนตัวอักษรทั้งหมดควรเป็นตัวพิมพ์ใหญ่หรือตัวพิมพ์เล็ก แต่ฉันแก้ไขปัญหาที่ละเอียดอ่อนทั้งหมด (ใน) สำหรับ enums ด้วยสิ่งนั้น:

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

การใช้งาน:

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

ฉันใช้การปรับเปลี่ยนโซลูชันของ Iago Fernándezและ Paul

ฉันมี enum ใน requestobject ของฉันซึ่งจำเป็นต้องเป็นตัวพิมพ์เล็กและใหญ่

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

-1

นี่คือวิธีที่บางครั้งฉันจัดการ enums เมื่อฉันต้องการ deserialize ในลักษณะที่ไม่คำนึงถึงตัวพิมพ์เล็กและใหญ่ (สร้างจากรหัสที่โพสต์ในคำถาม):

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

ถ้า enum ไม่สำคัญดังนั้นในคลาสของมันเองฉันมักจะเพิ่มวิธีการแยกวิเคราะห์แบบคงที่เพื่อจัดการกับสตริงตัวพิมพ์เล็ก


-1

Deserialize enum กับแจ็คสันนั้นง่ายมาก เมื่อคุณต้องการ deserialize enum ตามใน String จำเป็นต้องมี constructor, getter และ setter ไปยัง enum ของคุณนอกจากนี้คลาสที่ใช้ enum นั้นจะต้องมี 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 ของคุณคุณสามารถ deserialize คลาส Endpoint โดยใช้ ObjectMapper of 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();
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.