前言

objectMapper 对象:

  1. ObjectMapper objectMapper = new ObjectMapper();

User 类定义如下:

  1. public class User {
  2. private String name;
  3. private Integer age;
  4. private LocalDate date;
  5. private LocalTime time;
  6. private LocalDateTime dateTime;
  7. private Date juDate;
  8. private List<String> tags;
  9. private User user;
  10. // omit getter and setter
  11. }

JSON 字符串格式化输出

Jackson 默认转换的 JSON 字符串为一行,示例:

  1. User user = User.builder().name("张三").age(18).build();
  2. String json = objectMapper.writeValueAsString(user);
  3. System.out.println(json);

输出如下:

  1. {"name":"张三","age":18,"date":null,"time":null,"dateTime":null,"juDate":null,"tags":null,"user":null}

开启JSON字符串格式化配置:

  1. objectMapper.enable(SerializationFeature.INDENT_OUTPUT);

输出如下:

  1. {
  2. "name" : "张三",
  3. "age" : 18,
  4. "date" : null,
  5. "time" : null,
  6. "dateTime" : null,
  7. "juDate" : null,
  8. "tags" : null,
  9. "user" : null
  10. }

自定义反序列化函数

看下下面的示例,定义一个订单类如下:

  1. public class Order {
  2. private PayMethod payMethod;
  3. // omit getter and setter
  4. }

其中 PayMethod 是一个枚举类:

  1. public enum PayMethod {
  2. /** 支付宝 */
  3. ALI_PAY(1, "支付宝"),
  4. /** 微信 */
  5. WECHA_PAY(2, "微信");
  6. private final int code;
  7. private final String phrase;
  8. PayMethod(int code, String phrase) {
  9. this.code = code;
  10. this.phrase = phrase;
  11. }
  12. // omit getter
  13. }

如果直接输出看下这个对象转换的 JSON 是什么:

  1. ObjectMapper objectMapper = new ObjectMapper();
  2. objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
  3. Order order = new Order();
  4. order.setPayMethod(PayMethod.WECHA_PAY);
  5. String json = objectMapper.writeValueAsString(order);
  6. System.out.println(json);

输出如下:

  1. {
  2. "payMethod" : "WECHA_PAY"
  3. }

可以看到枚举值就是定义的具体枚举类,但是我们通常在序列化时给前端返回具体的 code 码,即输出如下:

  1. {
  2. "payMethod" : 2
  3. }

那这是怎么配置的呢?只需要在枚举类属性上使用 @JsonValue 注解接口,如下:

  1. public enum PayMethod {
  2. /** 支付宝 */
  3. ALI_PAY(1, "支付宝"),
  4. /** 微信 */
  5. WECHA_PAY(2, "微信");
  6. @JsonValue
  7. private final int code;
  8. private final String phrase;
  9. PayMethod(int code, String phrase) {
  10. this.code = code;
  11. this.phrase = phrase;
  12. }
  13. // omit getter
  14. }
注意
上面示例中的 @JsonValue 注解作用在属性字段上,但这是 Jackson2.9 才提供的功能。在该版本之前只能作用于方法,要注意这点!!!

同样的,如果想要输出的是具体的描述文字就在 phrase 属性上加上 @JsonValue 注解即可。注意,不能同时在多个属性上使用 @JsonValue 注解,否则会抛出异常。

问题是,现在已经输出了具体的 code 码了,那如果前端在请求是传递了这个 code 码消息转换器怎么反序列成枚举值呢?

使用 @JsonCreator 注解即可自定义反序列化方式。默认情况下,Jackson 在反序列化时会调用类的无参构造方法。现在使用 @JsonCreator 注解我们就可以随意定义了。

注意: @JsonCreator 注解只能使用在构造方法或类的静态方法上。

现在,我们只需要在枚举类中定义一个静态的反序列化方法即可:

  1. public enum PayMethod {
  2. /** 支付宝 */
  3. ALI_PAY(1, "支付宝"),
  4. /** 微信 */
  5. WECHA_PAY(2, "微信");
  6. @JsonValue
  7. private final int code;
  8. private final String phrase;
  9. PayMethod(int code, String phrase) {
  10. this.code = code;
  11. this.phrase = phrase;
  12. }
  13. // omit getter
  14. @JsonCreator
  15. public static PayMethod resolve(int code) {
  16. for (PayMethod payMethod : values()) {
  17. if (payMethod.getCode() == code) {
  18. return payMethod;
  19. }
  20. }
  21. return null;
  22. }
  23. }

看下示例:

  1. Order order = objectMapper.readValue("{\"payMethod\":2}", Order.class);
  2. System.out.println(order.getPayMethod());

输出如下:

  1. WECHA_PAY

transient 字段的处理

transient 是 Java 中的一个关键字。使用该关键字标识的属性字段在序列化与反序列化时会被忽略(如果不做特殊处理的话),这个机制在日常使用中是很有用的。

比如我们有一个 User 对象,在该对象中有一个 password 属性字段。在序列化保存时,如果不做处理那么相应的密码就会被保存到磁盘上,那么对于这个用户而言是绝大的风险。

但是如果在该字段上使用 transient 进行标识的话,那么在序列化时会自动忽略该属性字段,怎么样?是不是很有用?

但是 Jackson 在将对象序列化时是有问题的,因为它默认是不会对 transient 属性字段进行特殊处理的。

比如如下的 User 类:

  1. @Getter
  2. @Setter
  3. @ToString
  4. public class User implements Serializable {
  5. private static final long serialVersionUID = -1070592513103343024L;
  6. private String username;
  7. private transient String password;
  8. }

其中 password 使用 transient 关键字进行标识,在理想情况下在 JSON 序列化时应该不会输出该字段才对。

示例:

  1. ObjectMapper objectMapper = new ObjectMapper();
  2. User user = new User();
  3. user.setUsername("HanMeimei");
  4. user.setPassword("root@pwd");
  5. System.out.println(objectMapper.writeValueAsString(user));

输出结果:

  1. {
  2. "username" : "HanMeimei",
  3. "password" : "root@pwd"
  4. }

与我们预想的有些不太一样。

之所以 password 字段没有被忽略的原因是因为 Jackson 在序列化时使用的是 setter/getter 方法进行属性推断,而不是具体的属性字段。

关于这个问题在 stackoverflow 上也有相应的问题,并且给出了相应的解决方法:

https://stackoverflow.com/questions/21745593/why-jackson-is-serializing-transient-member-also

解决方法有如下几种方式:

使用 MapperFeature.PROPAGATE_TRANSIENT_MARKER 配置项

该配置项是用于开启对 transient 字段的特殊处理,我们可以在构造 ObjectMapper 对象时设置该序列化配置。

如下:

  1. ObjectMapper objectMapper = new ObjectMapper();
  2. objectMapper.configure(MapperFeature.PROPAGATE_TRANSIENT_MARKER, true);

这样再次输出时就是我们想要的结果了:

  1. {
  2. "username" : "HanMeimei"
  3. }

使用 setVisibility 方法

这种方法使用起来比较麻烦,不推荐。

在创建 objectMapper 对象时做下如下配置即可:

  1. ObjectMapper objectMapper = new ObjectMapper();
  2. objectMapper.setVisibility(
  3. objectMapper.getSerializationConfig()
  4. .getDefaultVisibilityChecker()
  5. .withFieldVisibility(JsonAutoDetect.Visibility.ANY)
  6. .withGetterVisibility(JsonAutoDetect.Visibility.NONE)
  7. );

效果就不演示了~

SpringBoot 配置项

如果是在 spring-boot 项目的话,使用 jackson 作为消息转换器的话。我们只需要在配置文件中加上如下配置就可以解决消息转换器的问题了:

  1. spring.jackson.mapper.propagate-transient-marker=true