Friday, August 18, 2017

JavaEE: Serialize & Deserialize Với Jackson Trong Rest API

Nếu ta sử dụng JavaEE và wildfly thì dữ liệu mà ta nhận về nếu là application/json thì đó là quá trình serialize của Jackson, như vấn đề sẽ đơn giản nếu như chúng ta không serialize class abstract. Vậy thì làm thế nào để mình config cho jackson biết là ta muốn serizlize của những instance của class abstract thay vì là class abstract. Xem một ví dụ sau về class abstract được serialize như thế nào trong jackson.

I. Serialize Trong Rest API 
1. Chúng ta có một class abstract Vihicle và các concrete class.
Vihicle, và Motobike, Bicycle kế thừa lớp Vihicle xem đoạn code bên dưới.
public class Vihicle {
 public enum Type {
  MOTOBIKE, BICYCLE
 };
 private int id;
 private String producer;
 private Type type;
}
public class Bicycle extends Vihicle { 
 private int weight;
}
public class Motobike extends Vihicle {
 private boolean useFuel;
 private int speed;
}
2. Ta có một danh sách object Vihicle gồm Motobike và Bicycle với nhau như sau
Hai object này sẽ được thêm là list Vihicle và chúng ta sẽ trả dữ liệu list này về cho user ta xem tiếp API bên dưới.
private Bicycle newBicycle() {
 Bicycle bicycle = new Bicycle();
 bicycle.setId(2);
 bicycle.setProducer("Trung Quoc");
 bicycle.setType(Type.BICYCLE);
 bicycle.setWeight(50);
 return bicycle;
}
private Motobike newMotobike() {
 Motobike motobike = new Motobike();
 motobike.setId(1);
 motobike.setProducer("Viet Nam");
 motobike.setSpeed(150);
 motobike.setUseFuel(true);
 motobike.setType(Type.MOTOBIKE);
 return motobike;
}
3. Ta có một API đơn giản trả về json của danh sach Vihicle
@Path("vihicle")
public class VihicleAPI {
 @GET
 @Produces(MediaType.APPLICATION_JSON)
 public List<Vihicle> getAll() {
  return newVihicles();
 }
}
OUTPUT: Mặc định khi ta trả về list Vihicle thì serialize sẽ chỉ có những thuộc tính của Vihicle mà sẽ không có các thuộc tính riêng lẻ của các Vihicle cụ thể như là Motobike hay Bicycle. Như ví dụ bên dươi là kết quả trả về dữ liệu của ta khi ta gọi API trả về list Vihicle nhưng điều này thì hoàn toàn không phải là mong muốn của ta.
[  
   {  
      "id":1,
      "producer":"Viet Nam",
      "type":"MOTOBIKE"
   },
   {  
      "id":2,
      "producer":"Trung Quoc",
      "type":"BICYCLE"
   }
]
EXPECT: Đây mơi là dữ liệu mà ta mong muốn API phải trả về cho ta tưng Vihicle cụ thể vậy làm sao ta có thể làm được điều này khi dùng API
[  
   {  
      "id":1,
      "producer":"Viet Nam",
      "type":"MOTOBIKE",
      "useFuel":true,
      "speed":150
   },
   {  
      "id":2,
      "producer":"Trung Quoc",
      "type":"BICYCLE",
      "weight":50
   }
]

Nhưng nếu chúng ta dùng Gson hoặc ObjectMapper thì chúng ta cũng serialize object như mong đợi xem cách dùng ObjectMapper trong phần này ta tạo một test case và làm hoàn toàn băng ObjectMapper hoặc Gson thì đều serialize được dữ liệu Json mà ta mong muốn, nhưng với API thì chúng ta cần làm thêm một số thứ nữa để được như json mong muốn.

Giải Pháp cho API
Ta thêm một số thông tin cho class Vihicle  để phân biệt từng class kế thừa như sau

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type", visible = true)
@JsonSubTypes({
 @Type(value = Motobike.class, name = "MOTOBIKE"),
 @Type(value = Bicycle.class, name = "BICYCLE")
})
public class Vihicle {
...
}
Khi chúng ta execute API lần nữa chúng ta sẽ thấy kết quả như mong đợi.
Như vậy là chúng ta đã xong phần trả về của Rest API, giờ làm sao chúng ta deserialize từ json kế thừa thành đúng Object

II. Deserialize Json Kế Thừa Object;
1. Dùng Gson.
Với case sử dụng Gson ta cần đăng ký thêm một số như sau.
Ta tạo một class VihicleDeserialize để custom deserialize của Gson như sau:
import java.lang.reflect.Type;
import java.util.Map;
import java.util.TreeMap;

import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;

import mhdanh.javacore.model.Bicycle;
import mhdanh.javacore.model.Motobike;
import mhdanh.javacore.model.Vihicle;

public class VihicleDeserialize implements JsonDeserializer {
 private static Map> map = new TreeMap<>();
 static {
  map.put("MOTOBIKE", Motobike.class);
  map.put("BICYCLE", Bicycle.class);
 }
 @Override
 public Vihicle deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
  Vihicle vihicle = null;
  String type = json.getAsJsonObject().get("type").getAsString();
  Class clazz = map.get(type);
  if (clazz != null) {
   vihicle = context.deserialize(json, clazz);
  }
  return vihicle;
 }
}

Tiếp theo ta sẽ tạo ra hai test case, thứ nhất là dùng Gson mặc định, thứ hai là mình đăng ký thêm cái class VihicleDeserialize của mình
@Test
public void test_deserialize() {
 String str = "[{\"id\":1,\"producer\":\"Viet Nam\",\"type\":\"MOTOBIKE\",\"useFuel\":true,\"speed\":150},{\"id\":2,\"producer\":\"Trung Quoc\",\"type\":\"BICYCLE\",\"weight\":50}]";
 Gson gson = new Gson();
 List vihicles = new ArrayList<>(Arrays.asList(gson.fromJson(str, Vihicle[].class)));
 Assert.assertFalse(vihicles.get(0) instanceof Motobike);
 Assert.assertFalse(vihicles.get(1) instanceof Bicycle);
}
@Test
public void test_deserialize_with_custom_deserialize() {
 String str = "[{\"id\":1,\"producer\":\"Viet Nam\",\"type\":\"MOTOBIKE\",\"useFuel\":true,\"speed\":150},{\"id\":2,\"producer\":\"Trung Quoc\",\"type\":\"BICYCLE\",\"weight\":50}]";
 Gson gson = new GsonBuilder().registerTypeAdapter(Vihicle.class, new VihicleDeserialize()).create();
 List vihicles = new ArrayList<>(Arrays.asList(gson.fromJson(str, Vihicle[].class)));
 Assert.assertTrue(vihicles.get(0) instanceof Motobike);
 Assert.assertTrue(vihicles.get(1) instanceof Bicycle);
}

2 Dùng ObjectMapper
Ta defined giống như trên Rest API

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type", visible = true)
@JsonSubTypes({
 @Type(value = Motobike.class, name = "MOTOBIKE"),
 @Type(value = Bicycle.class, name = "BICYCLE")
})
public class Vihicle {
...
}
Test Case
@Test
  public void test_deserialize_with_custom_deserialize_object_mapper() throws JsonParseException, JsonMappingException, IOException {
    String str = "[{\"id\":1,\"producer\":\"Viet Nam\",\"type\":\"MOTOBIKE\",\"useFuel\":true,\"speed\":150},{\"id\":2,\"producer\":\"Trung Quoc\",\"type\":\"BICYCLE\",\"weight\":50}]";
    
    ObjectMapper mapper = new ObjectMapper();     
    List<Vihicle> vihicles = new ArrayList<>(Arrays.asList(mapper.readValue(str, Vihicle[].class)));
    
    Assert.assertTrue(vihicles.get(0) instanceof Motobike);
    Assert.assertTrue(vihicles.get(1) instanceof Bicycle);
  }

No comments:

Post a Comment