Tổng hợp các annotation trong Jackson

Trong bài viết này, chúng ta sẽ cùng nhau tìm hiểu các annotation Jackson cung cấp sẵn, cách để tạo ra một custom annotation sử dụng trong Jackson và cuối cùng là vô hiệu hóa các annotation đang sử dụng.

Jackson Serialization Annotation

Jackson annotation được chia thành 2 loại: Serialization và Deserialization annotation. Phần đầi tiên, chúng ta sẽ tìm hiểu về Serialization annotation.

@JsonAnyGetter

Annotation @JsonAnyGetter là một method-level annotaion cho phép làm phẳng các cặp key-value của Map field sang Json. 

Cho ExtendableBean class

public class ExtendableBean {
    public String name;
    public ExtendableBean(String name) {
        this.name = name;
        properties = new HashMap<>();
    }
    private Map<String, String> properties;
    public Map<String, String> getProperties() {
        return properties;
    }
}

Nếu Serialization 1 ExtendableBean object sang Json string thông thường

ExtendableBean bean = new ExtendableBean("My bean");
bean.getProperties().put("attr1", "val1");
bean.getProperties().put("attr2", "val2");

String result = new ObjectMapper().writeValueAsString(bean);

Kết quả sẽ là: {“name”:”My bean”,”properties”:{“attr2″:”val2″,”attr1″:”val1”}}

Khi sử dụng @JsonAnyGetter các cặp key-value của Map field sẽ trở thành các thuộc tính chuẩn của Json string, bỏ ra ngoài properties key.

public class ExtendableBean {
    // .. 
    @JsonAnyGetter
    public Map<String, String> getProperties() {
        return properties;
    }
}

Kết quả sẽ là: {“name”:”My bean”,”attr2″:”val2″,”attr1″:”val1″}

@JsonGetter

@JsonGetter là một phiên bản thay thế cho @JsonProperty dùng để đánh dấu method là một getter method được sử dụng trong quá trình Serialization.

Ví dụ, chúng ta có thể chỉ định getTheName() như là một getter method của thuộc tính name trong MyBean class.

public class MyBean {
    public int id;
    private String name;
    
    public MyBean(int id, String name) {
        this.id = id;
        this.name = name;
    }

    @JsonGetter("name")
    public String getTheName() {
        return name;
    }
}

Và khi chuyển từ MyBean object sang Json string, Jackson sẽ sử dụng getTheName() method để lấy giá trị cho name key.

MyBean bean = new MyBean(1, "My bean");

String result = new ObjectMapper().writeValueAsString(bean);
// {"id":1,"name":"My bean"}

@JsonPropertyOrder

Chúng ta có thể sử dụng @JsonPropertyOrder để chỉ định thứ tự của các thuộc tính trong serialization.

@JsonPropertyOrder({"id", "name"})
public class MyBean {
    public int id;
    private String name;

    public MyBean(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public String getName() {
        return name;
  }
MyBean bean = new MyBean(1, "My bean");

String result = new ObjectMapper().writeValueAsString(bean);

// {"id":1,"name":"My bean"}

Note: Nếu @JsonPropertyOrder liệt kê thiếu thuộc tính nào trong class thì nó sẽ không được serialization. Ví dụ

@JsonPropertyOrder({"id"})
public class MyBean{...}

Thì kết quả sẽ là:

MyBean bean = new MyBean(1, "My bean");

String result = new ObjectMapper().writeValueAsString(bean);

// {"id":1}

Chúng ta cũng có thể sắp xếp các thuộc tính bảng chữ cái với @JsonPropertyOrder(alphabetic=true). Trong trường hợp này, kết quả sẽ là

{ "id":1, "name":"My bean"}

@JsonRawValue

@JsonRawValue annotation có thể chỉ cho Jackson biết giá trị của 1 thuộc tính chính là 1 property trong Json output.

public class RawBean {
    public String name;
 
    @JsonRawValue
    public String json;

   public RawBean(String name, String json) {
         this.name = name;
         this.json = json;
     }
}

Ví dụ đơn giản

RawBean bean = new RawBean("My bean", "{\"attr\":false}");

String result = new ObjectMapper().writeValueAsString(bean);
// {"name":"My bean","json":{"attr":false}}

@JsonValue

@JsonValue đại diện cho một method duy nhất sẽ được Jackson sử dụng để serialize object instance.

Ví dụ TypeEnumWithValue enum, sử dụng @JsonValue cho getName method. Như vậy trong quá trình serialization chỉ duy nhất method này được sử dụng.

public enum TypeEnumWithValue {
    // ... 
    @JsonValue
    public String getName() {
        return name;
    }
    public Integer getId() {
        return id;
    }
}

Kiểm thử

String enumAsString = new ObjectMapper()
                .writeValueAsString(TypeEnumWithValue.TYPE1);  // "Type A"

Nếu sử dụng @JsonValue nhiều hơn 1 method Jackson sẽ ném ra ngoại lệ.

public enum TypeEnumWithValue {
    TYPE1(1, "Type A"), TYPE2(2, "Type 2");

    private Integer id;
    private String name;

    TypeEnumWithValue(Integer id, String name) {
        this.id = id;
        this.name = name;
    }


    @JsonValue
    public String getName() {
        return name;
    }
    @JsonValue
    public Integer getId() {
        return id;
    }
}

Trong trường hợp này, nếu chạy với như ví dụ trên kết quả sẽ là: java.lang.IllegalArgumentException: Problem with definition of [AnnotedClass TypeEnumWithValue]: Multiple ‘as-value’ properties defined

@JsonRootName

@JsonRootName được sử dụng để wrapping một root name cho Json output khi chuyển Object sang Json string.

Wrapping nghĩa là thay vì User object khi chuyển sang Json

{
    "id": 1,
    "name": "John"
}

Sẽ được wrapping bởi root name:

{
    "User": {
        "id": 1,
        "name": "John"
}

Lưu ý @JsonRootName chỉ hoạt động khi WRAP_ROOT_VALUE được ObjectMapper cho phép. 

Ví dụ UserWithRoot sẽ được wrapping bởi root nameuser

@JsonRootName(value = "user")
public class UserWithRoot {
    public int id;
    public String name;
}

Để có được output: 

{
    "user":{
        "id":1,
        "name":"John"
    }
}

Chúng ta phải enable WRAP_ROOT_VALUE như sau

UserWithRoot user = new UserWithRoot (1, "John");
 
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.WRAP_ROOT_VALUE);
String result = mapper.writeValueAsString(user);

@JsonSerialize

@JsonSerialize dùng để chỉ ra một custom serializer được sử dụng trong Serialization.

Ví dụ sử dụng @JsonSerialize để chỉ định một custom serializer dùng để serialize thuộc tính eventDate trong EventWithSerializer class.

public class EventWithSerializer {
    public String name;

    @JsonSerialize(using = CustomDateSerializer.class)
    public Date eventDate;

    public EventWithSerializer(String name, Date eventDate) {
        this.name = name;
        this.eventDate = eventDate;
    }
}

Dưới đây là CustomDateSerializer dùng cho eventDate

public class CustomDateSerializer extends StdSerializer<Date> {
 
    private static SimpleDateFormat formatter 
      = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");
 
    public CustomDateSerializer() { 
        this(null); 
    } 
 
    public CustomDateSerializer(Class<Date> t) {
        super(t); 
    }
 
    @Override
    public void serialize(
      Date value, JsonGenerator gen, SerializerProvider arg2) 
      throws IOException{
        gen.writeString(formatter.format(value));
    }
}

Giờ chúng ta sẽ tiến hành kiểm thử kết quả sau khi serizalize eventDate sẽ có định dạng dd-MM-yyyy hh:mm:ss 

SimpleDateFormat df
        = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");

String toParse = "20-12-2014 02:30:00";
Date date = df.parse(toParse);
EventWithSerializer event = new EventWithSerializer("party", date);

String result = new ObjectMapper().writeValueAsString(event);
// {"name":"party","eventDate":"20-12-2014 02:30:00"}

Jackson Deserialization Annotation

Tiếp theo – chúng ta sẽ tìm hiểu các Deserialization annotation trong Jackson sử dụng trong quá trình deserializer Json sang Object.

@JsonCreator

Chúng ta có thể sử dụng @JsonCreator annotation để điều chỉnh constructor/factory được sử dụng trong deserialization. Điều này sẽ rất hữu ích khi cấu trúc của một số thuộc tính trong Json không trùng khớp với Java class.

Ví dụ chúng ta cần chuyển đoạn Json sau sang Java Object

{
    "id":1,
    "theName":"My bean"
}

Tuy nhiên, trong Java class không có thuộc tính nào là theName, thay vào đó là thuộc tính name có tương ứng với theName trong Json. Chúng ta có thể sử dụng @JsonCreator kết hợp với @JsonProperty để chỉ ra rằng theNamename là tương ứng với nhau trong deserialization.

public class BeanWithCreator {
    public int id;
    public String name;
 
    @JsonCreator
    public BeanWithCreator(
      @JsonProperty("id") int id, 
      @JsonProperty("theName") String name) {
        this.id = id;
        this.name = name;
    }
}

Hãy xem kết quả:

String json = "{\"id\":1,\"theName\":\"My bean\"}";

BeanWithCreator bean = new ObjectMapper()
        .readValue(json, BeanWithCreator.class);
System.out.println(bean.id + " - " + bean.name);
// 1 - My bean

@JacksonInject

@JacksonInject chỉ ra rằng một thuộc tính từ Java object sẽ lấy giá trị từ injection thay vì Json data.

public class BeanWithInject {
    @JacksonInject
    public int id;
     
    public String name;
}

Gía trị thuộc tính id sẽ được lấy từ InjectableValues object.

ObjectMapper objectMapper = new ObjectMapper();
String json = "{\"name\":\"My bean\"}";

InjectableValues inject = new InjectableValues.Std()
        .addValue(int.class, 1);


BeanWithInject bean = objectMapper.reader(inject)
        .forType(BeanWithInject.class).readValue(json);
System.out.println(bean.id + " - " + bean.name);
// 1 - My bean

@JsonAnySetter

@JsonAnySetter cho phép làm phẳng các cặp key-value trong Json string sang Map field.

Ví dụ ExtendableBean sử dụng @JsonAnySetter cho thuộc tính properties

public class ExtendableBean {
    public String name;
    private Map<String, String> properties = new HashMap<>();

    @JsonAnySetter
    public void add(String key, String value) {
        properties.put(key, value);
    }

    public Map<String, String> getProperties() {
        return properties;
    }
}

Đây là Json string cần chuyển deserialize 

{
    "name":"My bean",
    "attr2":"val2",
    "attr1":"val1"
}

Và kết quả cuối cùng sẽ là

String json
        = "{\"name\":\"My bean\",\"attr2\":\"val2\",\"attr1\":\"val1\"}";

ExtendableBean bean = new ObjectMapper()
        .readerFor(ExtendableBean.class)
        .readValue(json);
System.out.println(bean.getProperties().get("attr1"));
System.out.println(bean.getProperties().get("attr2"));

// val1
// val2

@JsonSetter

@JsonSetter là một phiên bản thay thế cho @JsonProperty dùng để đánh dấu method là một setter method được sử dụng trong quá trình Deserialization.

Ví dụ setTheName() method được xem như là một setter method cho thuộc tính name của MyBean class.

public class MyBean {
    public int id;
    private String name;

    @JsonSetter("name")
    public void setTheName(String name) {
        this.name = name;
    }

     public String getName() {
         return name;
     }
 }

Tiếp theo, chúng ta cần deserialization chuỗi Json sau

String json = "{\"id\":1,\"name\":\"My bean\"}";

MyBean bean = new ObjectMapper()
        .readerFor(MyBean.class)
        .readValue(json);
System.out.println(bean.id + " - " + bean.getName());
// 1 - My bean

@JsonDeserialize

@JsonDeserialize dùng để chỉ ra một custom deserializer được sử dụng trong quá Deserialization.

Hãy xem ví dụ dưới đây, chúng ta sẽ sử dụng @JsonDeserialize để deserialize eventDate với CustomDateDeserializer.

public class EventWithSerializer {
    public String name;
 
    @JsonDeserialize(using = CustomDateDeserializer.class)
    public Date eventDate;
}
public class CustomDateDeserializer
        extends StdDeserializer<Date> {

    private static SimpleDateFormat formatter
            = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");

    public CustomDateDeserializer() {
        this(null);
    }

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

    @Override
    public Date deserialize(
            JsonParser jsonparser, DeserializationContext context)
            throws IOException {

        String date = jsonparser.getText();
        try {
            return formatter.parse(date);
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
    }
}

Cuối cùng chúng ta sẽ kiểm thử với chuỗi Json: {“name”:”party”,”eventDate”:”20-12-2014 02:30:00″}

String json = "{\"name\":\"party\",\"eventDate\":\"20-12-2014 02:30:00\"}";

SimpleDateFormat df
        = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");
EventWithSerializer event = new ObjectMapper()
        .readerFor(EventWithSerializer.class)
        .readValue(json);;
System.out.println(event.name + " - " + event.eventDate);
// party - Sat Dec 20 02:30:00 ICT 2014

@JsonAlias

@JsonAlias định nghĩa một hoặc nhiều tên thuộc tính thay thế trong deserialization. 

public class AliasBean {
    @JsonAlias({ "fName", "f_name" })
    private String firstName;   
    private String lastName;
    
    public String getFirstName() {
         return firstName;
     }

     public String getLastName() {
         return lastName;
     }
}

Như vậy thuộc tính fName trong Json sẽ tương ứng với firstName, và f_name tương ứng với lastName trong Java class.

String json = "{\"fName\": \"John\", \"lastName\": \"Green\"}";
AliasBean aliasBean = new ObjectMapper().readerFor(AliasBean.class).readValue(json);
System.out.println(aliasBean.getLastName() + " " + aliasBean.getFirstName());
// Green John

Jackson Property Annotation

@JsonIgnoreProperties.

@JsonIgnoreProperties là 1 class-level anntation dùng để đánh dấu các thuộc tính sẽ bị Jackson bỏ qua trong serialization và deserialization.

Ví dụ chúng ta sẽ ignore thuộc tính id trong BeanWithIgnore class.

@JsonIgnoreProperties({"id"})
public class BeanWithIgnore {
    public int id;
    public String name;

    public BeanWithIgnore() {
    }

    public BeanWithIgnore(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

Giờ, chúng ta sẽ kiếm thử xem id có bị ignore trong serialization và deserialization không nhé.

BeanWithIgnore beanSerialization = new BeanWithIgnore(1, "My bean");

String result = new ObjectMapper()
        .writeValueAsString(beanSerialization);
// {"name":"My bean"}

String json = "{\"id\":1,\"name\":\"My bean\"}";

BeanWithIgnore beanDeserialization = new ObjectMapper().readValue(json, BeanWithIgnore.class);
System.out.println(beanDeserialization.id + " - "  + beanDeserialization.name);
 // 0 - My bean

Note: Kiểu dữ liệu nguyên thủy int, khi khởi tạo sẽ có giá trị mặc định là 0. Vì vậy giá trị id trong Deserialization vẫn bị Jackson ignore.

@JsonIgnore

Tương tự như @JsonIgnoreProperties, thế nhưng @JsonIgnore cho phép sử dụng với field-level. Trong đa số trường hợp, chúng ta nên sử dụng @JsonIgnore thay cho @JsonIgnoreProperties để tránh trường hợp phải sửa lại các thuộc tính trong @JsonIgnoreProperties khi thay đổi tên các thuộc tính của class.

public class BeanWithIgnore {
    @JsonIgnore
    public int id;
    public String name;

    public BeanWithIgnore() {
    }

    public BeanWithIgnore(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

Kết quả thu được sẽ tương tự với ví dụ trên

BeanWithIgnore beanSerialization = new BeanWithIgnore(1, "My bean");

String result = new ObjectMapper()
        .writeValueAsString(beanSerialization);
// {"name":"My bean"}

String json = "{\"id\":1,\"name\":\"My bean\"}";

BeanWithIgnore beanDeserialization = new ObjectMapper().readValue(json, BeanWithIgnore.class);
System.out.println(beanDeserialization.id + " - "  + beanDeserialization.name);
 // 0 - My bean

@JsonIgnoreType

@JsonIgnoreType có thể chú thích tại class level. Nó sẽ ignore toàn bộ thuộc tính của class này trong serialization và deserialization.

Ví dụ ignore toàn bộ các thuộc tính của Name class.

public class User {
    public int id;
    public Name name;

    public User(int id, Name name) {
        this.id = id;
        this.name = name;
    }

    @JsonIgnoreType
    public static class Name {
        public String firstName;
        public String lastName;

        public Name(String firstName, String lastName) {
            this.firstName = firstName;
            this.lastName = lastName;
        }
    }
}

Tiếp theo, kiểm tra trong serialization

User.Name name = new User.Name("John", "Doe");
User user = new User(1, name);

String result = new ObjectMapper()
        .writeValueAsString(user); // {"id":1}

@JsonInclude

Chúng ta có thể sử dụng @JsonInclude để loại trừ những thuộc tính empty/null/default value

@JsonInclude(Include.NON_NULL)
class MyBean {
    public int id;
    public String name;

    public MyBean(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

Khởi tạo MyBean object với name empty:

MyBean bean = new MyBean(1, null);

String result = new ObjectMapper()
        .writeValueAsString(bean); // {"id":1}

@JsonAutoDetect

@JsonAutoDetect cho phép Jackson tìm kiếm các thuộc tính kể cả private hay public để thêm chúng vào quá trình serialization và deserialization.

Ví dụ tạo PrivateBean với các thuộc tính private, không cung cấp getter, setter.

@JsonAutoDetect(fieldVisibility = Visibility.ANY)
public class PrivateBean {
    private int id;
    private String name;
    public PrivateBean(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

Mặc định Jackson sẽ sử dụng các getter, setter tương ứng để lấy giá trị trong quá trình serialization và deserialization. Tuy nhiên trong trường trường hợp này, @JsonAutoDetect chúng ta sẽ không cần triển khai settergetter method, 

PrivateBean bean = new PrivateBean(1, "My bean");

String result = new ObjectMapper()
        .writeValueAsString(bean);
 // {"id":1,"name":"My bean"}

Jackson Polymorphic Type Handling Annotations

Trong phần này, chúng ta sẽ xem các annotation dùng trong đa hình:

  • @JsonTypeInfo – mô tả các thông tin của supper class được dùng trong serialization và deserialization
  • @JsonSubTypes – mô tả thông tin sub-class.
  • @JsonTypeName – định nghĩa tên cho các sub-class.
 class Zoo {

     public Zoo() {
     }

     public Animal animal;


     public Zoo(Animal animal) {
         this.animal = animal;
     }

     @JsonTypeInfo(
            use = JsonTypeInfo.Id.NAME,
            property = "type")
    @JsonSubTypes({
            @JsonSubTypes.Type(value = Dog.class, name = "dog"),
            @JsonSubTypes.Type(value = Cat.class, name = "cat")
    })
    public static class Animal {
        public String name;

         public Animal() {
         }

         public Animal(String name) {
            this.name = name;
        }
    }

    @JsonTypeName("dog")
    public static class Dog extends Animal {
        public double barkVolume;

        public Dog(String name, double barkVolume) {
            super(name);
            this.barkVolume = barkVolume;
        }
    }

    @JsonTypeName("cat")
    public static class Cat extends Animal {
        public Cat() {
            super();
        }

        public boolean likesCream;
        public int lives;

        public Cat(String name) {
            super(name);
        }
    }
}

Chúng ta sẽ test thử với serialization Dog object.

Zoo.Dog dog = new Zoo.Dog("lacy", 1.3);
Zoo zoo = new Zoo(dog);

String result = new ObjectMapper()
        .writeValueAsString(zoo);
 // {"animal":{"type":"dog","name":"lacy","barkVolume":1.3}}

Đối với deserialization

String json = "{\"animal\":{\"name\":\"lacy\",\"type\":\"cat\"}}";

Zoo zoo = new ObjectMapper()
        .readerFor(Zoo.class)
        .readValue(json);

System.out.println(zoo.animal.name); // lacy
System.out.println(zoo.animal.getClass()); // class Zoo$Cat

Jackson General Annotation

Trong phần này, chúng ta sẽ cùng tìm hiểu các annotation general của Jackson.

@JsonProperty

Ở phần trên,  mình đã có nêu 2 annotation @JsonGetter, và @JsonSetter là annotation cụ thể trong serialization và deserialization. Tuy nhiên chúng ta có thể sử dụng @JsonProperty để chỉ định 1 method được xem như là setter, getter của một thuộc tính.

Ví dụ setTheNamegetTheName method sẽ được sử dụng là getter, setter cho thuộc tính name.

public class MyBean {
    public int id;
    private String name;

     public MyBean() { }

     public MyBean(int id, String name) {
         this.id = id;
         this.name = name;
     }

     @JsonProperty("name")
    public void setTheName(String name) {
        this.name = name;
    }

    @JsonProperty("name")
    public String getTheName() {
        return name;
    }
}
MyBean bean = new MyBean(1, "My bean");

String result = new ObjectMapper().writeValueAsString(bean);
System.out.println(result); // {"id":1,"name":"My bean"}

MyBean resultBean = new ObjectMapper()
        .readerFor(MyBean.class)
        .readValue(result);
System.out.println(resultBean.id + " - " + resultBean.getTheName());
 // 1 - My bean

@JsonFormat

@JsonFormat chỉ định định dạng khi serialization các giá trị Date/Time.

public class EventWithFormat {
    public EventWithFormat(String name, Date eventDate) {
        this.name = name;
        this.eventDate = eventDate;
    }

    public String name;

    @JsonFormat(
            shape = JsonFormat.Shape.STRING,
            pattern = "dd-MM-yyyy hh:mm:ss")
    public Date eventDate;
}

Và đây là kết quả kiếm thử

SimpleDateFormat df = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss");
df.setTimeZone(TimeZone.getTimeZone("UTC"));

String toParse = "20-12-2014 02:30:00";
Date date = df.parse(toParse);
EventWithFormat event = new EventWithFormat("party", date);

String result = new ObjectMapper().writeValueAsString(event);
System.out.println(result);
 // {"name":"party","eventDate":"20-12-2014 02:30:00"}

@JsonUnwrapped

@JsonUnwrapper định nghĩa các giá trị sẽ được làm phẳng trong serialization và deserialization.

Ví dụ Name class là một nested class của UnwrappedUser, chúng ta không muốn giá trị của Name object được đặt lồng bên trong thuộc tính UnwrappedUser

public class UnwrappedUser {
    public int id;

    public UnwrappedUser(int id, Name name) {
        this.id = id;
        this.name = name;
    }

    @JsonUnwrapped
    public Name name;

    public static class Name {
        public String firstName;
        public String lastName;

        public Name(String firstName, String lastName) {
            this.firstName = firstName;
            this.lastName = lastName;
        }
    }
}
UnwrappedUser.Name name = new UnwrappedUser.Name("John", "Doe");
UnwrappedUser user = new UnwrappedUser(1, name);

String result = new ObjectMapper().writeValueAsString(user);
System.out.println(result);
// {"id":1,"firstName":"John","lastName":"Doe"}

@JsonView

@JsonView anntaion cho phép chúng ta phân lớp các thuộc tính theo từng cấp trong serialization/deserialization.

Ví dụ tạo một Views class chia 2 cấp PublicInternal.

public class Views {
    public static class Public {}
    public static class Internal extends Public {}
}

Tiếp theo, tạo Item class sử dụng @JsonView để phân cấp theo View class.

public class Item {
    @JsonView(Views.Public.class)
    public int id;

    @JsonView(Views.Public.class)
    public String itemName;

    @JsonView(Views.Internal.class)
    public String ownerName;

     public Item(int id, String itemName, String ownerName) {
         this.id = id;
         this.itemName = itemName;
         this.ownerName = ownerName;
     }
 }

Serialization theo 2 cấp PublicInternal.

ObjectMapper objectMapper = new ObjectMapper();
Item item = new Item(2, "book", "John");

String publicJson = objectMapper
        .writerWithView(Views.Public.class)
        .writeValueAsString(item);
System.out.println(publicJson);
 // {"id":2,"itemName":"book"}

String internalJson = objectMapper
        .writerWithView(Views.Internal.class)
        .writeValueAsString(item);
System.out.println(internalJson);
 // {"id":2,"itemName":"book","ownerName":"John"}

@JsonManagedReference, @JsonBackReference

Giả sử ItemWithRefUserWithRef class thể hiện mối quan hệ hai chiều. Trong UserWithRef chứa một List ItemWithRef, ItemWithRef chứa một UserWithRef.

 class ItemWithRef {
    public int id;
    public String itemName;

    @JsonManagedReference
    public UserWithRef owner;

     public ItemWithRef(int id, String itemName, UserWithRef owner) {
         this.id = id;
         this.itemName = itemName;
         this.owner = owner;
     }
 }
 class UserWithRef {
    public int id;
    public String name;

    @JsonBackReference
    public List<ItemWithRef> userItems;

     public UserWithRef(int id, String name) {
         this.id = id;
         this.name = name;
         this.userItems = new ArrayList<>();
     }

     public void addItem(ItemWithRef item) {
         this.userItems.add(item);
     }
 }

Khi Jackson thực thi serialization hoặc deserialization sẽ dẫn đến vòng lặp vô tận, để giải quyết vấn đế này, sử dụng @JsonManagedReference cho ItemWithRef và @JsonBackReference cho UserWithRef.

UserWithRef user = new UserWithRef(1, "John");
ItemWithRef item = new ItemWithRef(2, "book", user);
user.addItem(item);

String result = new ObjectMapper().writeValueAsString(item);
System.out.println(result);
// {"id":2,"itemName":"book","owner":{"id":1,"name":"John"}}

Note: Chúng ta có thể thấy userItems đã bị lược bỏ trong kết quả trả về.

@JsonIdentityInfo

Ở ví dụ trên, kết quả thật sự vẫn chưa làm mình hài lòng, vì thuộc tính userItems bị lược bỏ trong kết quả trả về. @JsonIdentityInfor giúp chúng ta chỉ ra rằng các giá trị dùng để xác định Object trong serializing/deserializing để giải quyết các vấn đề trong các thuật toán đệ quy.

@JsonIdentityInfo(
        generator = ObjectIdGenerators.PropertyGenerator.class,
        property = "id")
public class ItemWithRef {
    public int id;
    public String itemName;

    public UserWithRef owner;

    public ItemWithRef(int id, String itemName, UserWithRef owner) {
        this.id = id;
        this.itemName = itemName;
        this.owner = owner;
    }
}
 /// ---------------------------------------------------------------
@JsonIdentityInfo(
        generator = ObjectIdGenerators.PropertyGenerator.class,
        property = "id")
public class UserWithRef {
    public int id;
    public String name;

    public List<ItemWithRef> userItems;

    public UserWithRef(int id, String name) {
        this.id = id;
        this.name = name;
        this.userItems = new ArrayList<>();
    }

    public void addItem(ItemWithRef item) {
        this.userItems.add(item);
    }
}

Kiểm tra kết quả tương tự khi sử dụng @JsonManagedReference, @JsonBackReference 

UserWithRef user = new UserWithRef(1, "John");
ItemWithRef item = new ItemWithRef(2, "book", user);
user.addItem(item);

String result = new ObjectMapper().writeValueAsString(item);
System.out.println(result);
// {"id":2,"itemName":"book","owner":{"id":1,"name":"John","userItems":[2]}}

@JsonFilter

Sử dụng @JsonFilter để chỉ định 1 bộ lọc trong quá trình serialization.

@JsonFilter("myFilter")
public class BeanWithFilter {
    public int id;
    public String name;

    public BeanWithFilter(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

Ví dụ Chỉ lấy các thuộc tính name BeanWithFilter object, bỏ qua tất cả các thuộc tính khác.

BeanWithFilter bean = new BeanWithFilter(1, "my bean");

FilterProvider filters
        = new SimpleFilterProvider().addFilter(
        "myFilter",
        SimpleBeanPropertyFilter.filterOutAllExcept("name"));

String result = new ObjectMapper()
        .writer(filters)
        .writeValueAsString(bean);
System.out.println(result); // {"name":"my bean"}

Custom Jackson Annotation

Ngoài các annotation mà Jackson cung cấp, chúng ta có thể tự tạo cho mình một Annotation đáp ứng các yêu cầu riêng của từng dự án.

Chúng ta có thể tạo ra một annotation với @JacksonAnnotationInside.

@Retention(RetentionPolicy.RUNTIME)
    @JacksonAnnotationsInside
    @JsonInclude(Include.NON_NULL)
    @JsonPropertyOrder({ "name", "id", "dateCreated" })
    public @interface CustomAnnotation {}

Tiếp theo, thử tạo một BeanWithCustomAnnotation class sử dụng CustomAnnotation được tạo ở trên.

@CustomAnnotation
public class BeanWithCustomAnnotation {
    public int id;
    public String name;
    public Date dateCreated;

    public BeanWithCustomAnnotation(int id, String name, Date dateCreated) {
        this.id = id;
        this.name = name;
        this.dateCreated = dateCreated;
    }
}

@CustomAnnotation sẽ sắp xếp Json output theo thứ tự: name, id, dateCreated và loại bỏ các thuộc tính nếu chúng có giá trị null/defaul/empty/.

BeanWithCustomAnnotation bean
        = new BeanWithCustomAnnotation(1, "My bean", null);

String result = new ObjectMapper().writeValueAsString(bean);
System.out.println(result);
 // {"name":"My bean","id":1}

Jackson MixIn Annotation

Tiếp theo, chúng ta sẽ tìm hiểu cách sử dụng MixIn annotation và lấy ví dụ lược bỏ thuộc tính kiểu User class

public class Item {
    public int id;
    public String itemName;
    public User owner;

    public Item(int id, String itemName, User owner) {
        this.id = id;
        this.itemName = itemName;
        this.owner = owner;
    }
}

public class User {
}


@JsonIgnoreType
public class MyMixInForIgnoreType {
}

So sánh trước và sau khi sử dụng MixIn

Item item = new Item(1, "book", null);

String result = new ObjectMapper().writeValueAsString(item);
System.out.println(result);
 // {"id":1,"itemName":"book","owner":null}

ObjectMapper mapper = new ObjectMapper();
mapper.addMixIn(User.class, MyMixInForIgnoreType.class);

result = mapper.writeValueAsString(item);
System.out.println(result);
 // {"id":1,"itemName":"book"}

Disable Jackson Annotation

Phần cuối trong bài viết này, chúng ta sẽ tìm cách vô hiệu hóa tất cả các Jackson annotation đã được sử dụng với MapperFeature.USE_ANNOTATIONS:

@JsonInclude(Include.NON_NULL)
@JsonPropertyOrder({ "name", "id" })
public class MyBean {
    public int id;
    public String name;
}
MyBean bean = new MyBean(1, null);
ObjectMapper mapper = new ObjectMapper();
mapper.disable(MapperFeature.USE_ANNOTATIONS);
String result = mapper.writeValueAsString(bean);

Kết quả trước khi sử dụng MapperFeature.USE_ANNOTATIONS.

{"id":1}

Sau khi sử dụng

{
    "id":1,
    "name":null
}

Tóm lược

Bài viết này đã giúp chúng ta đi sâu vào từng anntation của Jackson cung cấp, cũng như cách tạo ra một annotation riêng cho mình để sử dụng trong các trường hợp đặc biệt. 

Hy vọng qua bài biết này, chúng ta có thể sử dụng thuần thục các annotation này và sử dụng hợp lý cho từng yêu cầu cụ thể.

Nguồn tham khảo

https://www.concretepage.com/jackson-api/jackson-jsonignore-jsonignoreproperties-and-jsonignoretype

https://www.baeldung.com/jackson-annotations

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