Xử lý Optional field trong Json với Jackson

Trong bài viết này chúng ta sẽ cùng nhau tìm hiểu các vấn đề xoay quanh Optional class khi làm việc với Jackson và tìm ra cách giải quyết.

Maven dependency

Để sử dụng Jackson trong Project Mavem chúng ta cần thêm dependency:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.8</version>
</dependency>

Các bạn có thể tìm hiểu version mới nhất của Jackson tại Maven Central.

Problem Overview

Để diễn tả cho vấn đề khi sử dụng Optional trong Jackson, khởi tạo Book class với một field thông thường và một optional field.

public class Book {
    String title;
    Optional<String> subTitle;

     public Book() {
     }

     public Book(String title, Optional<String> subTitle) {
        this.title = title;
        this.subTitle = subTitle;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public Optional<String> getSubTitle() {
        return subTitle;
    }

    public void setSubTitle(Optional<String> subTitle) {
        this.subTitle = subTitle;
    }
}

Note: Lưu ý rằng việc sử dụng Optional field cho một class không được khuyến khích.

Serialization

Bây giờ, chúng ta sẽ tạo một Book object và serializer sang Json string.

public class Main {

    public static void main(String[] agrs) throws JsonProcessingException {

        ObjectMapper objectMapper = new ObjectMapper();
        Book book = new Book();
        book.setTitle("Oliver Twist");
        book.setSubTitle(Optional.of("The Parish Boy's Progress"));
        String result = objectMapper.writeValueAsString(book);
    }
}

Output: {“title”:”Oliver Twist”,”subTitle”:{“present”:true}}

Chúng ta có thể thấy giá trị của Optioal field sẽ không chứa giá trị của nó, mà chỉ chứa cấu trúc lồng present: true.

Trong trường hợp này, isPresent() method là public getter() method trong Optional class. Vì thế trong quá trình serialization Jackson sẽ sử dụng method này để lấy giá trị. Như vậy giá trị cho các Optional field trong trường hợp này chỉ là true or false tuỳ thuộc vào nó empty or not.

Deserialization

Serialization đã có kết quả không mong muốn rồi, vậy khi deserialization từ Json string sang Java object có chứa thuộc tính Optional.

public class Main {

    public static void main(String[] agrs) throws IOException {

        ObjectMapper objectMapper = new ObjectMapper();
        String bookJson = "{ \"title\": \"Oliver Twist\", \"subTitle\": \"foo\" }";
        Book result = objectMapper.readValue(bookJson, Book.class);
    }
}

Output: 

com.fasterxml.jackson.databind.JsonMappingException:
  Can not construct instance of java.util.Optional:
  no String-argument constructor/factory method to deserialize from String value (‘The Parish Boy’s Progress’)

Từ exception có thể thấy rằng Jackson cần một constructor nhận giá trị subtitle như một tham số chứ không chịu là một Optional field.

Solution

Những gì chúng ta muốn là Jackson có thể hiểu nếu Optional empty tương ứng với giá trị null, và nếu nó chứa giá trị thì sẽ được lấy ra sử dụng trong quá trình serialization và deserialization.

Để giải quyết vấn đề này, Jackson cung cấp các module làm việc với các kiểu dữ liệu trong JDK 8, trong đó có Optional.

Maven dependency

Trước tiên, để sử dụng các module này, thêm dependency sau:

<dependency>
   <groupId>com.fasterxml.jackson.datatype</groupId>
   <artifactId>jackson-datatype-jdk8</artifactId>
   <version>2.9.6</version>
</dependency>

Để sử dụng, chúng ta chỉ cần đăng ký với ObjectMapper object. 

ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new Jdk8Module());

Serialization

Ok, chúng ta sẽ kiểm tra lại xem serialization từ Book object sang Json như thế nào nhé

public class Main {

    public static void main(String[] agrs) throws IOException {

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new Jdk8Module());
        Book book = new Book();
        book.setTitle("Oliver Twist");
        book.setSubTitle(Optional.of("The Parish Boy's Progress"));
        String serializedBook = objectMapper.writeValueAsString(book);
    }
}

Output: {“title”:”Oliver Twist”,”subTitle”:”The Parish Boy’s Progress”}

Nếu giá trị của subtitle là empty, Json sẽ mang giá trị null.

public class Main {

    public static void main(String[] agrs) throws IOException {

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new Jdk8Module());
        Book book = new Book();
        book.setTitle("Oliver Twist");
        book.setSubTitle(Optional.empty());
        String serializedBook = objectMapper.writeValueAsString(book);
    }
}

Output: {“title”:”Oliver Twist”,”subTitle”:null}

Deserialization

Trong phần này, chúng ta cũng tiến hành kiểm tra lại deserialization trong trong trường hợp subtitle null hoặc not null.

public class Main {

    public static void main(String[] agrs) throws IOException {

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new Jdk8Module());
        String bookJson = "{ \"title\": \"Oliver Twist\", \"subTitle\": \"foo\" }";
        Book result = objectMapper.readValue(bookJson, Book.class);
        System.out.println(result.getTitle());
        System.out.println(result.getSubTitle().get());
    }
}

Output: 

Oliver Twist
foo

Với subtile null

public class Main {

    public static void main(String[] agrs) throws IOException {

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new Jdk8Module());
        String bookJson = "{ \"title\": \"Oliver Twist\", \"subTitle\": null }";
        Book result = objectMapper.readValue(bookJson, Book.class);
        System.out.println(result.getTitle());
        System.out.println(result.getSubTitle().isPresent());
    }
}

Output:

Oliver Twist
false

Tóm lược

Đây là một problem khá phổ biến khi Jackson làm việc với các kiểu dữ liệu mới trong JDK 8. Nếu chúng ta không để ý sẽ dẫn đến các lỗi không muốn và sẽ tốn rất nhiều thời gian. Hy vọng qua bài viết này, các bạn sẽ có kinh nghiệm khi xử lý các tình huống tương tự.

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