Jackson enum Nối tiếp và DeSerializer


225

Tôi đang sử dụng JAVA 1.6 và Jackson 1.9.9 Tôi đã có một enum

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

Tôi đã thêm @JsonValue, điều này dường như thực hiện công việc mà nó tuần tự hóa đối tượng thành:

{"event":"forgot password"}

nhưng khi tôi cố gắng khử lưu huỳnh tôi nhận được một

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

Tôi đang thiếu gì ở đây?


4
Bạn đã thử {"Event":"FORGOT_PASSWORD"}chưa Lưu ý giới hạn trên cả Sự kiện và FORGOT_PASSWORD.
OldCurmudgeon


Ai đến đây cũng kiểm tra cú pháp setter getter nếu bạn tuân theo quy ước đặt tên khác, tức là thay vì getValueđiều này GetValuekhông hiệu quả
Davut Gürbüz

Câu trả lời:


286

Giải pháp serializer / deserializer được chỉ ra bởi @xbakesx là một giải pháp tuyệt vời nếu bạn muốn tách hoàn toàn lớp enum của bạn khỏi biểu diễn JSON của nó.

Ngoài ra, nếu bạn thích một giải pháp độc lập, việc triển khai dựa trên @JsonCreator@JsonValuechú thích sẽ thuận tiện hơn.

Vì vậy, tận dụng ví dụ của @Stanley sau đây là một giải pháp khép kín hoàn chỉnh (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 hãy xem câu hỏi của tôi, tôi còn thiếu gì ở đây stackoverflow.com/questions/30525986/enum-is-not-binding
Mitchjot Singh

25
có thể rõ ràng đối với một số người, nhưng lưu ý rằng @ JsonValue được sử dụng để tuần tự hóa và @ JsonCreator để khử lưu huỳnh. Nếu bạn không làm cả hai bạn sẽ chỉ cần cái này hoặc cái kia.
acvcu

6
Tôi thực sự không thích giải pháp này vì thực tế đơn giản là bạn giới thiệu hai nguồn sự thật. Nhà phát triển sẽ luôn phải nhớ thêm tên ở hai nơi. Tôi rất thích một giải pháp chỉ làm đúng mà không cần trang trí nội bộ của enum bằng bản đồ.
mttdbrd

2
@ttdbrd làm thế nào về điều này để thống nhất sự thật? gist.github.com/Scuilion/036c53fd7fee2de89701a95822c0fb60
KevinO

1
Thay vì bản đồ tĩnh, bạn có thể sử dụng YourEnum.values ​​() cung cấp Mảng YourEnum và lặp lại trên đó
Valeriy K.

209

Lưu ý rằng kể từ khi cam kết này vào tháng 6 năm 2015 (Jackson 2.6.2 trở lên), giờ đây bạn có thể chỉ cần viết:

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

1
giải pháp tốt đẹp. Thật xấu hổ khi tôi bị mắc kẹt với 2.6.0 trong Dropwizard :-(
Clint Eastwood

1
Thật không may, điều này không trả lại tài sản khi chuyển đổi enum của bạn thành một chuỗi.
Nicholas

4
Tính năng này không được dùng nữa kể từ 2.8.
pqian

2
Giải pháp này hoạt động cho cả tuần tự hóa và giải tuần tự hóa trên Enum. Đã thử nghiệm trong 2.8.
Downhillski

1
Dường như hoàn toàn không bị phản đối: github.com/FasterXML/jackson-annotations/blob/master/src/main/ Lỗi
pablo

88

Bạn nên tạo một phương thức nhà máy tĩnh lấy đối số duy nhất và chú thích nó với @JsonCreator(có sẵn từ Jackson 1.2)

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

Tìm hiểu thêm về chú thích JsonCreator tại đây .


10
Đây là giải pháp sạch nhất và ngắn gọn nhất, phần còn lại chỉ là hàng tấn nồi hơi có thể (và nên!) Bằng mọi giá!
Clint Eastwood

4
@JSONValueđể tuần tự hóa và giải tuần tự hóa @JSONCreator.
Chiranjib

@JsonCreator public static Event valueOf(int intValue) { ... }để giải trừ intcho Eventđiều tra viên.
Eido95

1
@ClintEastwood liệu các giải pháp khác có nên tránh hay không phụ thuộc vào việc bạn có muốn tách các mối quan tâm nối tiếp / khử lưu huỳnh khỏi enum hay không.
Asa

44

Trả lời thực tế:

Trình giải nén mặc định cho enums sử dụng .name()để giải tuần tự hóa, vì vậy nó không sử dụng @JsonValue. Vì vậy, như @OldCurmudgeon đã chỉ ra, bạn cần truyền vào {"event": "FORGOT_PASSWORD"}để khớp với .name()giá trị.

Một tùy chọn khác (giả sử bạn muốn các giá trị ghi và đọc json giống nhau) ...

Thêm thông tin:

Vẫn còn một cách khác để quản lý quá trình tuần tự hóa và giải tuần tự hóa với Jackson. Bạn có thể chỉ định các chú thích này để sử dụng trình tuần tự hóa và trình giải tuần tự tùy chỉnh của riêng bạn:

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

Sau đó, bạn phải viết MySerializerMyDeserializertrông như thế này:

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

}

Cuối cùng, đặc biệt là để làm điều này với một enum JsonEnumnối tiếp với phương thức getYourValue(), trình tuần tự hóa và trình giải tuần tự của bạn có thể trông như thế này:

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
Việc sử dụng serializer tùy chỉnh (de) giết chết sự đơn giản (đang sử dụng Jackson rất đáng giá, btw), vì vậy điều này là cần thiết trong các tình huống thực sự nặng nề. Sử dụng @JsonCreator, như được mô tả dưới đây và kiểm tra nhận xét này
Dmitry Gryazin

1
Soluiton này là tốt nhất cho vấn đề hơi điên rồ được giới thiệu trong câu hỏi OP. Vấn đề thực sự ở đây là OP muốn trả về dữ liệu có cấu trúc ở dạng được hiển thị . Đó là, họ đang trả lại dữ liệu đã bao gồm một chuỗi thân thiện với người dùng. Nhưng để biến biểu mẫu được kết xuất trở lại thành định danh, chúng ta cần một số mã có thể đảo ngược việc chuyển đổi. Câu trả lời được chấp nhận của hacky muốn sử dụng bản đồ để xử lý việc chuyển đổi, nhưng cần bảo trì nhiều hơn. Với giải pháp này, bạn có thể thêm các kiểu liệt kê mới và sau đó các nhà phát triển của bạn có thể tiếp tục với công việc của họ.
mttdbrd

34

Tôi đã tìm thấy một giải pháp rất hay và súc tích, đặc biệt hữu ích khi bạn không thể sửa đổi các lớp enum như trong trường hợp của tôi. Sau đó, bạn nên cung cấp ObjectMapper tùy chỉnh với một tính năng nhất định được bật. Những tính năng này có sẵn kể từ Jackson 1.6. Vì vậy, bạn chỉ cần viết toString()phương thức trong enum của bạn.

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

Có nhiều tính năng liên quan đến enum có sẵn, xem tại đây:

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


10
không chắc chắn tại sao bạn cần phải mở rộng lớp học. bạn có thể kích hoạt tính năng này trên một phiên bản của ObjectMapper.
mttdbrd

+1 vì anh ấy đã chỉ cho tôi [READ | WRITE] _ENUMS_USING_TO_STRING mà tôi có thể sử dụng trong ứng dụng
Spring.yml

8

Thử cái này.

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

Bạn có thể tùy chỉnh khử lưu huỳnh cho bất kỳ thuộc tính nào.

Khai báo lớp deserialize của bạn bằng cách sử dụng annotationJsonDeserialize ( import com.fasterxml.jackson.databind.annotation.JsonDeserialize) cho thuộc tính sẽ được xử lý. Nếu đây là một Enum:

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

Bằng cách này, lớp của bạn sẽ được sử dụng để giải tuần tự hóa thuộc tính. Đây là một ví dụ đầy đủ:

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

Nathaniel Ford, đã tốt hơn?
Fernando Gomes

1
Vâng, đây là một câu trả lời tốt hơn nhiều; nó cung cấp một số bối cảnh. Mặc dù vậy, tôi sẽ đi xa hơn và thảo luận về lý do tại sao việc thêm quá trình khử lưu huỳnh theo cách này giải quyết trở ngại cụ thể của OP.
Nathaniel Ford

5

Có nhiều cách tiếp cận khác nhau mà bạn có thể thực hiện để thực hiện giải tuần tự hóa đối tượng JSON thành enum. Phong cách yêu thích của tôi là tạo ra một lớp bên trong:

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

Đây là một ví dụ khác sử dụng các giá trị chuỗi thay vì bản đồ.

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

Trong bối cảnh của một enum, sử dụng @JsonValuenow (kể từ 2.0) hoạt động cho tuần tự hóa và giải tuần tự hóa .

Theo javadoc chú thích cho@JsonValue :

LƯU Ý: khi được sử dụng cho các enum Java, một tính năng bổ sung là giá trị được trả về bởi phương thức chú thích cũng được coi là giá trị để giải tuần tự hóa, không chỉ là Chuỗi JSON để tuần tự hóa như. Điều này là có thể vì tập hợp các giá trị Enum là hằng số và có thể xác định ánh xạ, nhưng không thể thực hiện chung cho các loại POJO; như vậy, điều này không được sử dụng cho quá trình khử lưu huỳnh POJO.

Vì vậy, có Eventenum được chú thích giống như trên hoạt động (cho cả tuần tự hóa và giải tuần tự hóa) với jackson 2.0+.


3

Bên cạnh việc sử dụng @JsonSerialize @JsonDeserialize, bạn cũng có thể sử dụng serializationFeature và DeserializationFeature (liên kết jackson) trong trình ánh xạ đối tượng.

Chẳng hạn như DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE, cung cấp loại enum mặc định nếu loại được cung cấp không được xác định trong lớp enum.


0

Cách đơn giản nhất mà tôi tìm thấy là sử dụng chú thích @ JsonFormat.Shape.OB DỰ ÁN cho enum.

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

0

Trong trường hợp của tôi, đây là những gì đã giải quyết:

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

Tuần tự hóa và giải tuần tự hóa json sau:

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

Tôi hy vọng nó sẽ giúp!

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.