Jackson 데이터 바인딩 열거 형 대 / 소문자 구분 안 함


99

대소 문자를 구분하지 않는 열거 형 값을 포함하는 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() {

    }

}

이 경우 JSON을 deserialize하면 "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();
    }

그리고 그것은 효과가있었습니다. 그러나 이것이 나에게 해킹처럼 보이기 때문에 더 나은 솔루션이 있는지 궁금합니다.

커스텀 디시리얼라이저를 작성할 수도 있지만 열거 형을 사용하는 다양한 POJO가 있으며 유지 관리가 어려울 것입니다.

누구든지 적절한 명명 규칙을 사용하여 열거 형을 직렬화 및 역 직렬화하는 더 나은 방법을 제안 할 수 있습니까?

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

어떤 버전의 Jackson을 사용하고 있습니까? 이 JIRA 살펴보세요 jira.codehaus.org/browse/JACKSON-861을
알렉세이 가브 릴 로프에게

나는 Jackson 2.2.3을 사용하고 있습니다
tom91136 2014-06-11

OK 난 그냥 2.4.0-RC3 업데이트
tom91136

답변:


38

버전 2.4.0에서는 모든 Enum 유형에 대한 사용자 지정 serializer를 등록 할 수 있습니다 ( github 문제에 대한 링크 ). 또한 Enum 형식을 인식하는 표준 Enum deserializer를 직접 교체 할 수 있습니다. 다음은 예입니다.

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 중요 값 (열거 형 이름)의 이름을 클라이언트 중요 값 (예쁜 인쇄)으로 분리한다는 것입니다. 예를 들어 HTML 데이터 유형을 HTML_DATA_TYPE으로 변경하려는 경우 지정된 키가있는 경우 외부 API에 영향을주지 않고 그렇게 할 수 있습니다.
Sam Berry

1
이것은 좋은 시작이지만 열거 형이 JsonProperty 또는 JsonCreator를 사용하는 경우 실패합니다. Dropwizard에는 보다 강력한 구현 인 FuzzyEnumModule 이 있습니다.
Pixel Elephant

134

잭슨 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

8
2.9.2를 사용하고 있는데 작동하지 않습니다. 원인 : com.fasterxml.jackson.databind.exc.InvalidFormatException : 문자열 "male"에서 .... Gender` 유형의 값을 역 직렬화 할 수 없음 : 선언 된 Enum 인스턴스 이름 중 하나가 아닌 값 : [FAMALE, MALE]
Jordan Silva

@JordanSilva는 확실히 v2.9.2에서 작동합니다. 검증을위한 테스트가 포함 된 전체 코드 예제를 추가했습니다. 귀하의 경우에 무슨 일이 있었는지 모르겠지만 jackson-databind2.9.2로 예제 코드를 실행하면 예상대로 작동합니다.
davnicwil

5
봄 부팅을 사용하여, 당신은 단순히 재산 추가 할 수 있습니다spring.jackson.mapper.accept-case-insensitive-enums=true
아르네 Burmeister

1
@JordanSilva 아마도 당신은 내가 한 것처럼 매개 변수를 가져 오는 데 열거 형을 역 직렬화하려고합니까? =) 내 문제를 해결하고 여기에 대답했습니다. 그것은 도움이 될 수 있습니다 희망
콘스탄틴 Zyubin

83

내 프로젝트에서 동일한 문제가 발생하여 문자열 키와 사용 @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. 편집했습니다. 또한 JAX-RS와 잘 어울리도록 method의 이름을 "fromString"으로 변경했습니다 .
Sam Berry

1
나는이 접근 방식을 좋아했지만 덜 장황한 변형을 선택했습니다. 아래를 참조하십시오.
linqu

2
분명히 key필드는 필요하지 않습니다. 에서 getKey, 당신은 할 수 단지return name().toLowerCase()
yair

1
나는 열거 형의 이름을 json과 다른 이름으로 지정하려는 경우 키 필드를 좋아합니다. 필자의 경우 레거시 시스템은 보내는 값에 대해 정말 축약되고 기억하기 어려운 이름을 보내고이 필드를 사용하여 Java 열거 형의 더 나은 이름으로 변환 할 수 있습니다.
그린 치

47

Jackson 2.6부터 간단하게 다음과 같이 할 수 있습니다.

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

전체 예는 이 요점을 참조하십시오 .


26
이렇게하면 문제가 해결됩니다. 이제 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())루프가있는 직접 인스턴스화입니다. 이것은 매우 많은 열거 형에 대한 문제가 될 수 있습니다. 물론, valueOf코드에서 피하는 IllegalArgumentException을 throw 할 수 있으므로 예외 검사보다 null 검사를 선호하는 경우 좋은 이점입니다.
Patrick M

22

2.1.xJackson과 함께 Spring Boot 를 2.9사용하는 경우 다음 애플리케이션 속성을 사용하면됩니다.

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


3

GET 매개 변수 에서 대 / 소문자를 무시하고 Enum을 deserialize하려는 경우 ACCEPT_CASE_INSENSITIVE_ENUMS를 활성화해도 아무런 효과가 없습니다. 이 옵션은 body deserialization 에만 작동하므로 도움이되지 않습니다 . 대신 다음을 시도하십시오.

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에게 사과하면서 그의 대답은 내가 필요로하는 것에 가까웠지만 이해하지 못했습니다. 그래서 내가 생각하는 방법은 다음과 같습니다.

하나의 열거 형 유형을 대소 문자를 구분하지 않고 역 직렬화하려는 경우 (예 : 전체 애플리케이션의 동작을 수정하고 싶지 않거나 수정할 수없는 경우) 하위 클래스 화 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;
}

0

com.fasterxml.jackson.databind.util.EnumResolver에 문제가 있습니다. HashMap을 사용하여 enum 값을 보유하고 HashMap은 대소 문자를 구분하지 않는 키를 지원하지 않습니다.

위의 답변에서 모든 문자는 대문자 또는 소문자 여야합니다. 하지만 열거 형에 대한 모든 (in) 민감한 문제를 수정했습니다.

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 솔루션의 수정을 사용했습니다.

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

0

jackson에서 대소 문자를 구분하지 않는 열거 형 역 직렬화를 허용하려면 아래 속성을 application.properties스프링 부트 프로젝트 의 파일에 추가하기 만하면 됩니다.

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

yaml 버전의 속성 파일이있는 경우 파일에 아래 속성을 추가 application.yml합니다.

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

-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 기반 열거 형을 deserialize하려면 생성자, enum에 대한 getter 및 setter가 필요합니다. 또한 해당 열거 형을 사용하는 클래스에는 String이 아닌 DataType을 매개 변수로받는 setter가 있어야합니다.

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();
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.