Mục lục
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à unit và meters.
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, metters và field 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