Jackson 열거 형 직렬화 및 DeSerializer


225

JAVA 1.6과 Jackson 1.9.9를 사용하고 있습니다.

public enum Event {
    FORGOT_PASSWORD("forgot password");

    private final String value;

    private Event(final String description) {
        this.value = description;
    }

    @JsonValue
    final String value() {
        return this.value;
    }
}

@JsonValue를 추가했는데 객체를 직렬화하는 작업을 수행하는 것 같습니다.

{"event":"forgot password"}

하지만 역 직렬화하려고하면

Caused by: org.codehaus.jackson.map.JsonMappingException: Can not construct instance of com.globalrelay.gas.appsjson.authportal.Event from String value 'forgot password': value not one of declared Enum instance names

내가 여기서 무엇을 놓치고 있습니까?


4
시도 했습니까 {"Event":"FORGOT_PASSWORD"}? Event 및 FORGOT_PASSWORD의 한도를 모두 유의하십시오.
OldCurmudgeon

비슷한 방법
Vadzim

누가 즉 대신 다른 이름 지정 규칙을 따르는 경우도 게터 세터 구문을 확인 여기에 온 getValueGetValue작동하지 않습니다
Davut Gürbüz

답변:


286

@xbakesx 가 지적한 serializer / deserializer 솔루션은 열거 형 클래스를 JSON 표현에서 완전히 분리하려는 경우 훌륭한 솔루션 입니다.

당신이 기반으로 독립적 인 솔루션 구현을 선호하는 경우 또는, @JsonCreator@JsonValue주석이 더 편리 할 것입니다.

@Stanley 의 예제를 활용 하여 다음은 완전한 자체 포함 솔루션 (Java 6, Jackson 1.9)입니다.

public enum DeviceScheduleFormat {

    Weekday,
    EvenOdd,
    Interval;

    private static Map<String, DeviceScheduleFormat> namesMap = new HashMap<String, DeviceScheduleFormat>(3);

    static {
        namesMap.put("weekday", Weekday);
        namesMap.put("even-odd", EvenOdd);
        namesMap.put("interval", Interval);
    }

    @JsonCreator
    public static DeviceScheduleFormat forValue(String value) {
        return namesMap.get(StringUtils.lowerCase(value));
    }

    @JsonValue
    public String toValue() {
        for (Entry<String, DeviceScheduleFormat> entry : namesMap.entrySet()) {
            if (entry.getValue() == this)
                return entry.getKey();
        }

        return null; // or fail
    }
}

@Agusti 내가 거기에 무엇을 놓치고, 내 질문에서 봐 주시기 바랍니다 stackoverflow.com/questions/30525986/enum-is-not-binding
Prabjot 싱

25
어쩌면 일부에게는 분명하지만 @ JsonValue는 직렬화에 사용되고 @ JsonCreator는 직렬화 해제에 사용됩니다. 두 가지를 모두 수행하지 않으면 둘 중 하나만 필요합니다.
acvcu

6
나는 당신이 진실의 두 가지 근원을 소개한다는 단순한 사실 때문에이 솔루션을 싫어합니다. 개발자는 항상 두 곳에 이름을 추가해야합니다. 열거 형의 내부를 맵으로 장식하지 않고 올바른 일을하는 솔루션을 선호합니다.
mttdbrd

2
@ttdbrd 진실을 통일하기 위해 어떻습니까? gist.github.com/Scuilion/036c53fd7fee2de89701a95822c0fb60
KevinO

1
정적 맵 대신 YourEnum.values ​​()를 사용하여 YourEnum 배열을 제공하고 반복 할 수 있습니다.
Valeriy K.

209

주의로 있음 이 커밋 년 6 월 2015 년 (잭슨 2.6.2 이상)에 당신은 지금 간단하게 작성할 수 있습니다 :

public enum Event {
    @JsonProperty("forgot password")
    FORGOT_PASSWORD;
}

1
좋은 해결책. Dropwizard에 번들로 포함 된 2.6.0으로 고정 된 것은 부끄러운
Clint Eastwood

1
불행히도 이것은 열거 형을 문자열로 변환 할 때 속성을 반환하지 않습니다.
Nicholas

4
이 기능은 2.8부터 사용되지 않습니다.
pqian

2
이 솔루션은 Enum에서 직렬화 및 직렬화 해제에 모두 작동합니다. 2.8에서 테스트되었습니다.
Downhillski

1
더 이상 사용되지 않는 것 같습니다 : github.com/FasterXML/jackson-annotations/blob/master/src/main/…
pablo

88

단일 인수를 사용하여 주석을 추가하는 정적 팩토리 메소드를 작성해야합니다 @JsonCreator(Jackson 1.2부터 사용 가능).

@JsonCreator
public static Event forValue(String value) { ... }

JsonCreator 주석에 대한 자세한 내용은 여기를 참조하십시오 .


10
이것은 가장 깨끗하고 간결한 솔루션이며 나머지는 모든 비용으로 피할 수있는 보일러 플레이트입니다.
클린트 이스트우드

4
@JSONValue직렬화하고 @JSONCreator역 직렬화합니다.
Chiranjib

@JsonCreator public static Event valueOf(int intValue) { ... }열거자를 직렬화 해제 int합니다 Event.
Eido95

1
@ClintEastwood 다른 솔루션을 피해야하는지 여부는 직렬화 / 직렬화 해제 문제를 열거 형에서 분리할지 여부에 따라 다릅니다.
Asa

44

실제 답변 :

열거 형의 기본 deserializer는 deserialize하는 데 사용하므로 .name()를 사용 하지 않습니다 @JsonValue. @OldCurmudgeon이 지적했듯이 값 {"event": "FORGOT_PASSWORD"}과 일치 하도록 전달해야 .name()합니다.

다른 옵션 (쓰기 및 읽기 json 값이 동일하다고 가정) ...

더 많은 정보:

Jackson으로 직렬화 및 역 직렬화 프로세스를 관리하는 또 다른 방법이 있습니다. 고유 한 사용자 정의 시리얼 라이저 및 디시리얼라이저를 사용하도록 이러한 주석을 지정할 수 있습니다.

@JsonSerialize(using = MySerializer.class)
@JsonDeserialize(using = MyDeserializer.class)
public final class MyClass {
    ...
}

그런 다음 다음 MySerializerMyDeserializer같이 작성해야합니다 .

MySerializer

public final class MySerializer extends JsonSerializer<MyClass>
{
    @Override
    public void serialize(final MyClass yourClassHere, final JsonGenerator gen, final SerializerProvider serializer) throws IOException, JsonProcessingException
    {
        // here you'd write data to the stream with gen.write...() methods
    }

}

MyDeserializer

public final class MyDeserializer extends org.codehaus.jackson.map.JsonDeserializer<MyClass>
{
    @Override
    public MyClass deserialize(final JsonParser parser, final DeserializationContext context) throws IOException, JsonProcessingException
    {
        // then you'd do something like parser.getInt() or whatever to pull data off the parser
        return null;
    }

}

마지막 JsonEnum으로, 메소드 getYourValue()를 사용하여 직렬화 하는 열거 형 으로이 작업을 수행 하는 경우 직렬 변환기와 직렬 변환기는 다음과 같이 보일 수 있습니다.

public void serialize(final JsonEnum enumValue, final JsonGenerator gen, final SerializerProvider serializer) throws IOException, JsonProcessingException
{
    gen.writeString(enumValue.getYourValue());
}

public JsonEnum deserialize(final JsonParser parser, final DeserializationContext context) throws IOException, JsonProcessingException
{
    final String jsonValue = parser.getText();
    for (final JsonEnum enumValue : JsonEnum.values())
    {
        if (enumValue.getYourValue().equals(jsonValue))
        {
            return enumValue;
        }
    }
    return null;
}

3
custom (de) serializer를 사용하면 단순함이 사라집니다 (Jackson을 사용하는 btw 가치가 있음). 이것은 실제로 무거운 상황에서 필요합니다. 사용 @JsonCreator은으로 아래에 설명하고, 확인 이 댓글을
드미트리 Gryazin에게

1
이 솔루션은 OPs 질문에 도입 된 다소 미친 문제에 가장 적합합니다. 여기서 실제 문제는 OP가 구조화 된 데이터를 렌더링 된 형식 으로 반환하려고한다는 것입니다. 즉, 이미 사용자 친화적 인 문자열이 포함 된 데이터를 반환합니다. 그러나 렌더링 된 양식을 식별자로 다시 변환하려면 변환을 취소 할 수있는 코드가 필요합니다. 해키 수락 답변은 변환을 처리하기 위해 맵을 사용하려고하지만 더 많은 유지 보수가 필요합니다. 이 솔루션을 사용하면 새로운 열거 유형을 추가 한 다음 개발자가 작업을 수행 할 수 있습니다.
mttdbrd

34

매우 훌륭하고 간결한 솔루션을 찾았습니다. 특히 열거 형 클래스를 수정할 수없는 경우에 유용합니다. 그런 다음 특정 기능이 활성화 된 사용자 정의 ObjectMapper를 제공해야합니다. 이러한 기능은 Jackson 1.6부터 사용할 수 있습니다. 따라서 toString()열거 형에 메소드 만 작성하면됩니다 .

public class CustomObjectMapper extends ObjectMapper {
    @PostConstruct
    public void customConfiguration() {
        // Uses Enum.toString() for serialization of an Enum
        this.enable(WRITE_ENUMS_USING_TO_STRING);
        // Uses Enum.toString() for deserialization of an Enum
        this.enable(READ_ENUMS_USING_TO_STRING);
    }
}

사용 가능한 열거 관련 기능이 더 있습니다. 여기를 참조하십시오.

https://github.com/FasterXML/jackson-databind/wiki/Serialization-Features https://github.com/FasterXML/jackson-databind/wiki/Deserialization-Features


10
왜 수업을 확장해야하는지 잘 모르겠습니다. ObjectMapper 인스턴스에서이 기능을 활성화 할 수 있습니다.
mttdbrd

그가 나를 봄 애플리케이션에서 사용할 수있는 [READ | WRITE] _ENUMS_USING_TO_STRING을 지적했기 때문에 + 1.yml
HelLViS69

8

이 시도.

public enum Event {

    FORGOT_PASSWORD("forgot password");

    private final String value;

    private Event(final String description) {
        this.value = description;
    }

    private Event() {
        this.value = this.name();
    }

    @JsonValue
    final String value() {
        return this.value;
    }
}

6

모든 속성의 역 직렬화를 사용자 정의 할 수 있습니다.

import com.fasterxml.jackson.databind.annotation.JsonDeserialize처리 할 속성에 annotationJsonDeserialize ( )를 사용하여 역 직렬화 클래스를 선언하십시오 . 이것이 열거 형인 경우 :

@JsonDeserialize(using = MyEnumDeserialize.class)
private MyEnum myEnum;

이런 식으로 클래스는 속성을 역 직렬화하는 데 사용됩니다. 이것은 전체 예입니다.

public class MyEnumDeserialize extends JsonDeserializer<MyEnum> {

    @Override
    public MyEnum deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
        JsonNode node = jsonParser.getCodec().readTree(jsonParser);
        MyEnum type = null;
        try{
            if(node.get("attr") != null){
                type = MyEnum.get(Long.parseLong(node.get("attr").asText()));
                if (type != null) {
                    return type;
                }
            }
        }catch(Exception e){
            type = null;
        }
        return type;
    }
}

나다니엘 포드, 나아 졌어?
Fernando Gomes

1
그렇습니다. 이것은 훨씬 더 나은 대답입니다. 컨텍스트를 제공합니다. 그래도 더 나아가서 이러한 방식으로 역 직렬화를 추가하면 OP의 특정 장애를 해결하는 이유에 대해 논의합니다.
Nathaniel Ford

5

JSON 객체를 열거 형으로 역 직렬화하기 위해 수행 할 수있는 다양한 방법이 있습니다. 내가 가장 좋아하는 스타일은 내부 클래스를 만드는 것입니다.

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.hibernate.validator.constraints.NotEmpty;

import java.util.Arrays;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.fasterxml.jackson.annotation.JsonFormat.Shape.OBJECT;

@JsonFormat(shape = OBJECT)
public enum FinancialAccountSubAccountType {
  MAIN("Main"),
  MAIN_DISCOUNT("Main Discount");

  private final static Map<String, FinancialAccountSubAccountType> ENUM_NAME_MAP;
  static {
    ENUM_NAME_MAP = Arrays.stream(FinancialAccountSubAccountType.values())
      .collect(Collectors.toMap(
        Enum::name,
        Function.identity()));
  }

  private final String displayName;

  FinancialAccountSubAccountType(String displayName) {
    this.displayName = displayName;
  }

  @JsonCreator
  public static FinancialAccountSubAccountType fromJson(Request request) {
    return ENUM_NAME_MAP.get(request.getCode());
  }

  @JsonProperty("name")
  public String getDisplayName() {
    return displayName;
  }

  private static class Request {
    @NotEmpty(message = "Financial account sub-account type code is required")
    private final String code;
    private final String displayName;

    @JsonCreator
    private Request(@JsonProperty("code") String code,
                    @JsonProperty("name") String displayName) {
      this.code = code;
      this.displayName = displayName;
    }

    public String getCode() {
      return code;
    }

    @JsonProperty("name")
    public String getDisplayName() {
      return displayName;
    }
  }
}

4

다음은 맵 대신 문자열 값을 사용하는 다른 예입니다.

public enum Operator {
    EQUAL(new String[]{"=","==","==="}),
    NOT_EQUAL(new String[]{"!=","<>"}),
    LESS_THAN(new String[]{"<"}),
    LESS_THAN_EQUAL(new String[]{"<="}),
    GREATER_THAN(new String[]{">"}),
    GREATER_THAN_EQUAL(new String[]{">="}),
    EXISTS(new String[]{"not null", "exists"}),
    NOT_EXISTS(new String[]{"is null", "not exists"}),
    MATCH(new String[]{"match"});

    private String[] value;

    Operator(String[] value) {
        this.value = value;
    }

    @JsonValue
    public String toStringOperator(){
        return value[0];
    }

    @JsonCreator
    public static Operator fromStringOperator(String stringOperator) {
        if(stringOperator != null) {
            for(Operator operator : Operator.values()) {
                for(String operatorString : operator.value) {
                    if (stringOperator.equalsIgnoreCase(operatorString)) {
                        return operator;
                    }
                }
            }
        }
        return null;
    }
}

4

열거 형의 맥락에서 @JsonValuenow (2.0 이후)를 사용하면 직렬화 직렬화 해제에 작동합니다 .

jackson-annotations javadoc에@JsonValue 따르면 :

참고 : Java 열거 형에 사용할 때 주석이 추가 된 메소드에 의해 리턴 된 값은 직렬화 할 JSON 문자열뿐만 아니라 직렬화 해제 할 값으로 간주됩니다. Enum 값 세트가 일정하고 맵핑을 정의 할 수 있지만 POJO 유형에 대해서는 일반적으로 수행 할 수 없으므로 가능합니다. 따라서 POJ deserialization에는 사용되지 않습니다.

따라서 Event잭슨 2.0 이상에서 위의 작업 (직렬화 및 역 직렬화 모두 가능)과 같이 주석이 주석 처리되었습니다.


3

@JsonSerialize @JsonDeserialize를 사용하는 것 외에도 개체 매퍼에서 SerializationFeature 및 DeserializationFeature (잭슨 바인딩)를 사용할 수도 있습니다.

DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE와 같이 제공된 열거 형이 열거 형 클래스에 정의되어 있지 않은 경우 기본 열거 형 유형을 제공합니다.


0

내가 찾은 가장 간단한 방법은 열거 형에 @ JsonFormat.Shape.OBJECT 주석을 사용하는 것입니다.

@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum MyEnum{
    ....
}

0

제 경우에는 이것이 해결되었습니다.

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;

@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum PeriodEnum {

    DAILY(1),
    WEEKLY(2),
    ;

    private final int id;

    PeriodEnum(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return this.name();
    }

    @JsonCreator
    public static PeriodEnum fromJson(@JsonProperty("name") String name) {
        return valueOf(name);
    }
}

다음 json을 직렬화하고 역 직렬화합니다.

{
  "id": 2,
  "name": "WEEKLY"
}

도움이 되길 바랍니다!

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