Xử lý Enum trong Json – Jackson

Trong bài viết này, chúng ta sẽ tìm hiểu các cách để serialize/deserialize Java Enum trong Jackson. 

Enum Pre-defined 

Trước tiên chúng ta cần định nghĩa một enum dùng xuyên suốt trong bài viết này.

public enum Distance {
    KILOMETER ("km", 1000),
    MILE ("miles", 1609.34),
    METER ("meters", 1),
    INCH ("inches", 0.0254),
    CENTIMETER ("cm", 0.01),
    MILLIMETER ("mm", 0.001);

    private String unit;
    private final double meters;

    Distance(String unit, double meters) {
        this.unit = unit;
        this.meters = meters;
    }

     public String getUnit() {
         return unit;
     }

     public void setUnit(String unit) {
         this.unit = unit;
     }

     public double getMeters() {
         return meters;
     }
 }

Serialize Enum sang Json

Default Value Enum

Mặc định, Jackson sẽ xem Enum như một String khi serialize nó sang Json.

String result = new ObjectMapper()
                  .writeValueAsString(Distance.MILE);

Output: “MILE”

Nếu muốn kết quả là

{"unit":"miles","meters":1609.34}

Thì khi serialize Distance sang Json, Jackson cần xem Enum như là 1 object. Để khi serialize nó sẽ lấy giá trị các field trong Enum thay vì xem nó như là một String. Trong trường hợp Distance là unitmeters.

Enum Object

Bắt đầu từ Jackson 2.1.2, chúng có thể cấu hình trong Jackson để xử lý việc này với @JsonFormat.

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

Khi đó, kết quả sẽ chứa các field của Enum như một đặc tả của nó.

String result = new ObjectMapper()
                   .writeValueAsString(Distance.MILE);

Output: 

{"unit":"miles","meters":1609.34}

Enum với @JsonValue

Một cách đơn giản khác để kiểm soát kết quả serialize enum là sử dụng @JsonValue. Sử dụng @JsonValue. để đánh dấu 1 field tham gia và serialization.

Ví dụ chỉ lấy field meters khi serialize Distance sang Json.

public enum Distance { 
    ...
  
    @JsonValue
    public String getMeters() {
        return meters;
    }
}

Như vậy giá trị của Distance sẽ được đại diện bởi getMeters() method.

String result = new ObjectMapper()
                .writeValueAsString(Distance.MILE); // 1609.34

Custom Serializer cho Enum

Các phiên bản Jackson 2.1.2 trở về trước, hoặc với các yêu cầu chuyên biệt khi serialize Enum sang Json, chúng ta có thể tạo một custom serialize để sử dụng riêng với @JsonSerialize annotation.

Ví dụ serialize Distance sang Json chứa 3 thông số: unit, mettersfield name chứa giá trị của Enum chuyển sang String.

public class DistanceSerializer extends StdSerializer<Distance> {

    public DistanceSerializer() {
        super(Distance.class);
    }

    public DistanceSerializer(Class t) {
        super(t);
    }

    @Override
    public void serialize(
            Distance distance, JsonGenerator generator, SerializerProvider provider) throws IOException {
        generator.writeStartObject();
        generator.writeFieldName("name");
        generator.writeString(distance.name());
        generator.writeFieldName("unit");
        generator.writeString(distance.getUnit());
        generator.writeFieldName("meters");
        generator.writeNumber(distance.getMeters());
        generator.writeEndObject();
    }
}

Bây giờ, chúng ta có thể sử dụng DistanceSerializer class như một custom serializer cho Distance Enum:

@JsonSerialize(using = DistanceSerializer.class)
public enum TypeEnum { ... }

Như vậy, nếu Serialize Distance.MILE sang Json

String result = new ObjectMapper()
                .writeValueAsString(Distance.MILE); // 1609.34

Output:

{"name":"MILE","unit":"miles","meters":1609.34}

Deserialize Json sang Enum

Default value

Không giống serialize, deserialize sẽ khởi tạo Enum với constructor tương ứng, nên giá trị các field trong enum cũng được khởi tạo theo.

String json = "\"KILOMETER\"}";

Distance distance = new ObjectMapper().readValue(json, Distance.class);
System.out.println(distance);
System.out.println(distance.getMeters() + "/" + distance.getUnit());

Output:

KILOMETER
1000.0/km

Deserialize với @JsonProperty

@JsonProperty có thể dùng để đánh dấu các giá trị tương ứng của Enum khi deserialize Json sang Distance

Ví dụ KILOMETER tương ứng với KILOMETER(“km”, 1000), MILLIMETER tương ứng MILLIMETER(“mm”, 0.001).

public enum Distance {
    @JsonProperty("KILOMETER")
    KILOMETER("km", 1000),

    @JsonProperty("MILE")
    MILE("miles", 1609.34),

    @JsonProperty("METER")
    METER("meters", 1),

    @JsonProperty("INCH")
    INCH("inches", 0.0254),

    @JsonProperty("CENTIMETER")
    CENTIMETER("cm", 0.01),

    @JsonProperty("MILLIMETER")
    MILLIMETER("mm", 0.001);

    private String unit;
    private double meters;
    // constructor, getter, setter
}

Nếu deserialize INCH sang Distance

String json = "\"INCH\"";

Distance distance = new ObjectMapper().readValue(json, Distance.class);
System.out.println(distance);
System.out.println(distance.getMeters() + "/" + distance.getUnit());

Output:

INCH
0.0254/inches

Deserialize với @JsonCreator

Jackson sẽ gọi method được chú thích với @JsonCreator annotation để khởi tạo một instance khi deserialize.

Vì vậy chúng ta có thể khởi tạo method dùng để khởi tạo Object theo cấu trúc định nghĩa sẵn.

Ví dụ deserialize Distance Enum với Json có cấu trúc 

"{
    \"unit\": \"value\",
    \"meters\": value
}"
public enum Distance {
    KILOMETER("km", 1000),
    MILE("miles", 1609.34),
    METER("meters", 1),
    INCH("inches", 0.0254),
    CENTIMETER("cm", 0.01),
    MILLIMETER("mm", 0.001);

    private String unit;
    private final double meters;

    @JsonCreator
    public static Distance forValues(@JsonProperty("unit") String unit,
                                     @JsonProperty("meters") double meters) {
        for (Distance distance : Distance.values()) {
            if (distance.unit.equals(unit) && Double.compare(distance.meters, meters) == 0) {
                return distance;
            }
        }

        return null;
    }
    // constructor, getter, setter
}

Deserialize Json theo cấu trúc định nghĩa như trên:

String json = "{\"unit\": \"km\", \"meters\": 1000}";

Distance distance = new ObjectMapper().readValue(json, Distance.class);
System.out.println(distance);
System.out.println(distance.getMeters() + "/" + distance.getUnit());

Output:

KILOMETER
1000.0/km

Custom Deserializer

Nếu dự án đang dùng các phiên bản cũ chưa hỗ trợ @JsonCreator hoặc có nhu cầu chuyên biệt, chúng ta có thể tạo ra một custom deserializer sử dụng trong deserialization. 

public class CustomEnumDeserializer extends StdDeserializer<Distance> {

    public CustomEnumDeserializer() {
        this(null);
    }

    public CustomEnumDeserializer(Class<?> vc) {
        super(vc);
    }

    @Override
    public Distance deserialize(JsonParser jsonParser, DeserializationContext ctxt)
            throws IOException {
        JsonNode node = jsonParser.getCodec().readTree(jsonParser);

        String unit = node.get("unit").asText();
        double meters = node.get("meters").asDouble();

        for (Distance distance : Distance.values()) {

            if (distance.getUnit().equals(unit) && Double.compare(
                    distance.getMeters(), meters) == 0) {
                return distance;
            }
        }

        return null;
    }
}

Tiếp theo, sử dụng CustomEnumDeserializer cho Distance enum với @JsonDeserialize annotation.

@JsonDeserialize(using = CustomEnumDeserializer.class)
public enum Distance {
   ...
}
String json = "{\"unit\": \"km\", \"meters\": 1000}";

Distance distance = new ObjectMapper().readValue(json, Distance.class);
System.out.println(distance);
System.out.println(distance.getMeters() + "/" + distance.getUnit());

Output: 

KILOME9TER
1000.0/km

Tóm lược

Qua bài viết này, chúng ta đã tìm ra các cách giải quyết khác nhau trong các trường hợp cụ thể khi xử lý Enum trong Json. Hy vọng nó sẽ giúp các bạn giải quyết các vấn đề đang gặp phải với Enum trong Json. 

Nguồn tham khảo

https://www.baeldung.com/jackson-serialize-enums

https://stackoverflow.com/questions/12468764/jackson-enum-serializing-and-deserializer

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x