Xử lý Map trong Json với Jackson

Trong bài viết này, chúng ta sẽ cùng nhau tìm hiểu cách Jackson serialization và deserialization Java Map.

Maven dependency

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

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

Ở đây, mình sử dụng phiên bản 2.9.8, các bạn có thể sử dụng phiên bản mới hơn tại maven central.

Map Serialization

Java Map là một collection dạng key-value thường được xem là đối tượng không thân thiện trong serialization. Trong phần này chúng ta sẽ chia Map ra các dạng khác nhau.

Map<String, String> Serialization

Đối với trường hợp đơn giản này, chúng ta chỉ cần khởi tạo một Map<String, String> và serializtion sang Json

Map<String, String> map = new HashMap<>();
map.put("key", "value");
 
ObjectMapper mapper = new ObjectMapper();
String jsonResult = mapper.writerWithDefaultPrettyPrinter()
  .writeValueAsString(map);

Output

{
  "key" : "value"
}

Map<Object, String> Serialization

Chúng ta có thể serialize một map chứa key là một Java class tự định nghĩa với một vài bước bổ sung.

Khởi tạo một MyPair class đại diện cho key của map

public class MyPair {
    private String first;
    private String second;

    public MyPair(String first, String second) {
        this.first = first;
        this.second = second;
    }

    public String getFirst() {
        return first;
    }

    public void setFirst(String first) {
        this.first = first;
    }

    public String getSecond() {
        return second;
    }

    public void setSecond(String second) {
        this.second = second;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof MyPair)) return false;
        MyPair myPair = (MyPair) o;
        return Objects.equals(first, myPair.first) &&
                Objects.equals(second, myPair.second);
    }

    @Override
    public int hashCode() {
        return Objects.hash(first, second);
    }

    @Override
    @JsonValue
    public String toString() {
        return first + " and " + second;
    }
}

Note: Chú thích @JsonValue cho toString() method của MyPair để đảm bảo Jackson sẽ sử dụng toString() khi serialize MyPair object.

public class Main {
    public static void main(String[] agrs) throws IOException {
        ObjectMapper mapper = new ObjectMapper();
        Map<MyPair, String> map = new HashMap<>();
        MyPair key = new MyPair("Abbott", "Costello");
        map.put(key, "Comedy");

        String jsonResult = mapper.writerWithDefaultPrettyPrinter()
                .writeValueAsString(map);

        System.out.println(jsonResult);

    }
}

Output:

{
  "Abbott and Costello" : "Comedy"
}

Map<Object, Object> Serialization

Một trường hợp phức tạp hơn đó là cả key-value đều là Java class do chúng ta tự định nghĩa. Tuy nhiên đừng lo lắng, vì toString() method của MyPair class đã được chú thích @JsonValue, nên việc serialize value sang Json cũng tương tự key trong map.

public class Main {

    public static void main(String[] agrs) throws IOException {
        Map<MyPair, MyPair> map = new HashMap<>();

        MyPair mapKey = new MyPair("Abbott", "Costello");
        MyPair mapValue = new MyPair("Comedy", "1940s");
        map.put(mapKey, mapValue);

        String jsonResult = new ObjectMapper().writerWithDefaultPrettyPrinter()
                .writeValueAsString(map);

        System.out.println(jsonResult);

    }
}

Output:

  "Abbott and Costello" : "Comedy and 1940s"
}

Nếu không sử dụng @JsonValue cho MyPair#toString() method, thì toString() vẫn sẽ được áp dụng cho key của map khi serialize.

public class MyPair {
    @Override
    // @JsonValue
    public String toString() {
        return first + " and " + second;
    }
    // ....
}

Thì khi serialize Map<MyPair, MyPair> giá trị của value sẽ có cấu trúc tương tự map trong Java.

public class Main {

    public static void main(String[] agrs) throws IOException {
        Map<MyPair, MyPair> map = new HashMap<>();

        MyPair mapKey = new MyPair("Abbott", "Costello");
        MyPair mapValue = new MyPair("Comedy", "1940s");
        map.put(mapKey, mapValue);

        String jsonResult = new ObjectMapper().writerWithDefaultPrettyPrinter()
                .writeValueAsString(map);

        System.out.println(jsonResult);

    }
}

Output

{
  "Abbott and Costello" : {
    "first" : "Comedy",
    "second" : "1940s"
  }
}

Map Deserialization

Map<String, String> Deserialization

Tương tự, với trường hợp đơn giản nhất của map khi deserialization. Lưu ý trong phần này, chúng ta sẽ sử dụng TypeReference để bao ngoài HashMap class để sử dụng trong readValue().

public class Main {

    public static void main(String[] agrs) throws IOException {
        String jsonInput = "{\"key\": \"value\"}";
        TypeReference<HashMap<String, String>> typeRef  = new TypeReference<HashMap<String, String>>() {};
        Map<String, String> map = new ObjectMapper()
                        .readValue(jsonInput, typeRef);
        System.out.println(map.get("Key"));  // value
    
    }
}

Map<Object, String> Deserialization

Đối với Map<Object, String> chúng ta có thể deserialize thông qua một số bước bổ sung.

Đối với các Json string được serialize ở phần trên, chúng đã sử dụng toString() method để lấy giá trị trong serialization với cấu trúc :

frist and second

Như vậy, việc đầu tiên chúng ta cần làm là khởi tạo một constructor nhận một String với cấu trúc trên và tiến hành tách chuỗi để có được firstsecond

public MyPair {
   // ....
   public MyPair(String both) {
        String[] pairs = both.split("and");
        this.first = pairs[0].trim();
        this.second = pairs[1].trim();
    }
}

Tiếp theo, chúng ta cần chỉ định 1 custom deserializer sử dụng khi khi deserialize Json sang Map<MyPair, String>. 

public class MyPairDeserializer extends KeyDeserializer {

    @Override
    public MyPair deserializeKey(
            String key,
            DeserializationContext ctxt) {

        return new MyPair(key);
    }
}

Chỉ định MyPairDeserializer cho map với @JsonDeserialize và tiến hành kiểm thử.

public class Main {

    @JsonDeserialize(keyUsing = MyPairDeserializer.class)
    private static TypeReference<HashMap<MyPair, String>> typeRef;

    public static void main(String[] agrs) throws IOException {
        String jsonInput = "{\"Abbott and Costello\" : \"Comedy\"}";

        typeRef = new TypeReference<HashMap<MyPair, String>>() {};
        Map<MyPair, String> map = new ObjectMapper().readValue(jsonInput, typeRef);

        MyPair key = map.keySet().iterator().next();
        System.out.println(key.toString());
        System.out.println(map.get(key));


    }
}

Output:

Abbott and Costello
Comedy

Map<Object, Object> Deserialization

Phần cuối cùng, thay đổi cấu trúc của Map<Object, Object> với keyvalue đều là các class được định nghĩa.

Thật may, chúng ta sẽ không cần làm gì thêm, MyPairDeserializer sẽ áp dụng cho cả key-value dạng MyPair

public class Main {

    @JsonDeserialize(keyUsing = MyPairDeserializer.class)
    private static TypeReference<HashMap<MyPair, String>> typeRef;

    public static void main(String[] agrs) throws IOException {
        String jsonInput = "{\"Abbott and Costello\" : \"Comedy and 1940s\"}";
        TypeReference<HashMap<MyPair, MyPair>> typeRef
                = new TypeReference<HashMap<MyPair, MyPair>>() {};
        Map<MyPair,MyPair> map = new ObjectMapper().readValue(jsonInput, typeRef);


        MyPair key = map.keySet().iterator().next();
        System.out.println(key.toString());
        System.out.println(map.get(key).toString());


    }
}

Output:

Abbott and Costello
Comedy and 1940s

Tóm lược

Như vậy là chúng ta đã biết cách serialize và deserialize Java Map trong Jackson. Đây là một dạng dữ liệu yêu cầu nhiều xử lý khi làm việc với Json, thông thường chúng ta phải thống nhất cấu trúc của Json khi serialize/deserialize để không gây ra các lỗi không đáng có.

Nguồn tham khảo

https://www.baeldung.com/jackson-maphttps://www.baeldung.com/jackson-map

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