实现 toJson() 方法

Java 中 POJO 的toString()方法和我们预期的 JSON 格式不符合,而如果直接覆盖它写一个生成 JSON 的又不合适,因为当需要那种格式的时候就没得用的,所以本着各司其职的原则,我们来实现一个toJson()方法。

序列化库选择

我比较熟悉的就是 Jackson 和 Gson,其中 Gson 的使用较为简单,所以这里用 Gson 来实现。其实主要原因是 Jackson 在序列化对向的时候会抛出异常,而 Gson 就不会。这里简单验证一下。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package fun.happyhacker.json;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;

public class JsonTest {
    public static void main(String[] args) {
        Employee employee = new Employee();
        employee.setAge(10);
        employee.setId(1);
        employee.setName("John");

        ObjectMapper objectMapper = new ObjectMapper();
        String jackson = "";
        try {
            jackson = objectMapper.writeValueAsString(employee);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        System.out.println(jackson);

        String gson = new Gson().toJson(employee);
        System.out.println(gson);

        System.out.println(new GsonBuilder().serializeNulls().create().toJson(employee));
    }
}
1
2
3
{"id":1,"name":null,"age":10}
{"id":1,"age":10}
{"id":1,"name":null,"age":10}

所以,起码在序列化 JSON 这方面,可以认为两个库的作用是一致的,但 Gson 用起来很简单。

给每个 POJO 添加toJson方法

所以是不是就要人肉给每个 POJO 添加这个方法了,比如上面提到的Employee

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package fun.happyhacker.json;

import com.google.gson.GsonBuilder;
import lombok.Data;

@Data
public class Employee {
    private int id;
    private String name;
    private int age;

    public String toJson() {
        return new GsonBuilder().serializeNulls().create().toJson(this);
    }
}

虽说是可行,但明显有太多了模板代码可以消除了。

利用 lombok

其实这个方法我都是在 lombok 相关的帖子下看到的,考虑到目前 lombok 并没有提供类似@ToJson这种注解,那么可以利用它的ExtensionMethod来实现。

首先创建一个扩展方法集

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package fun.happyhacker.json;

import com.google.gson.GsonBuilder;

import java.io.Serializable;

public class Extensions {
    public static <T extends Serializable> String toJson(T t) {
        return new GsonBuilder().serializeNulls().create().toJson(t);
    }
}

这里面我加了一点小的限制,让POJO 类必须要可以序列化才能使用toJson()方法。

然后在需要调用 toJson() 方法的所在的类上加上 @ExtensionMethod 注解

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package fun.happyhacker.json;

import lombok.experimental.ExtensionMethod;

@ExtensionMethod({Extensions.class})
public class JsonTest {
    public static void main(String[] args) {
        Employee employee = new Employee();
        employee.setAge(10);
        employee.setId(1);
        employee.setName("John");

        System.out.println(employee.toJson());
    }
}

这时你会发现 IDEA 识别不了这个toJson()方法,但没关系,它是可以正常执行的,这是 IDEA 的 lombok 扩展不支持而已。在这一点上 Eclipse 已经领先了,虽然在其他所有方面 Eclipse 都是惨遭碾压。

简单讲就是 lombok 在编译期把employee.toJson()这个方法改写成了new GsonBuilder().serializeNulls().create().toJson(employee),这也解释了为什么Extensions中的方法需要是静态的。

总结

虽说是解决了一部分问题,但我觉得这个问题解决的不够优雅,按我们正常的思维,这个注解是应该加到Employee这个类上的,而不是加到调用它的类上。所以就很尴尬了。不知道能不能参与到 lombok 官方项目中,给它加上这个功能。

经过简单的搜索发现这个问题已经被讨论过无数遍了,核心问题是核心开发者认为这个和项目设计之初的目标不符合(我觉得这点站不住脚),他们拿toString()来做比较,说本来每个类也都有toString,只是没有实现,但几乎没有类存在toJson这个方法。其实本质上还是因为实现起来太复杂了,没有一个轻量级、高性能的 JSON 序列化库可以用,虽然 Jackson 和 Gson 都可以达成目的,但他们认为都太重了。

所以这已经不是技术问题了,而是哲学问题。可能这些“库”作者压根不能理解我们应用开发者的痛点吧。

Licensed under CC BY-NC-SA 4.0
Built with Hugo
Theme Stack designed by Jimmy