序列化 - Jackson

Jackson 是当前用的比较广泛的,用来序列化和反序列化 json 的 Java 的开源框架。Jackson 社区相对比较活跃,更新速度也比较快。

Spring MVC 的默认 json 解析器便是 Jackson。(目前最新稳定版本: 2.13.4)

Jackson 的核心模块由三部分组成,一般是引入三个包并保证他们 3 的版本一样:

  • jackson-core,核心包,提供基于"流模式"解析的相关 API,它包括 JsonPaser 和 JsonGenerator。 Jackson 内部实现正是通过高性能的流模式 API 的 JsonGenerator 和 JsonParser 来生成和解析 json。
  • jackson-annotations,注解包,提供标准注解功能;
  • jackson-databind ,数据绑定包, 提供基于"对象绑定" 解析的相关 API ( ObjectMapper ) 和"树模型" 解析的相关 API (JsonNode);基于"对象绑定" 解析的 API 和"树模型"解析的 API 依赖基于"流模式"解析的 API。
<!-- jackson -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.13.4</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>2.13.4</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.13.4</version>
</dependency>

Jackson 中最最最常用的 API 就是基于"对象绑定" 的类 :ObjectMapper,它位于 jackson-databind中,你可以只导入这一个包

ObjectMapper 是什么?

object 英文含义为对象,mappper 英文含义为转换/映射。组合起来,ObjectMapper 就是 对象转换

ObjectMapper 封装提供了 一系列对象转换 的方法,对象转换将其细分后又分为 序列化反序列化 ,在 Java 当中, 序列化 与 Java-SDK 提供得接口 Serializable 密切相关。

举例说明,在网络通信中,我们的 Http 接口一般要求的都是传递字符串, 而在开发时,我们书写的是 class 这种数据类型,所以要将 class 数据转换为字符串。

@SpringBootTest
class UserControllerTest {
    
    

    @Test
    public void useObjectMapper() throws JsonProcessingException {
    
    

        ObjectMapper objectMapper = new ObjectMapper();

        String userString = "{\"phone\": \"13366668888\", \"pwd\" : \"123456\"}";

        UserEntity user = objectMapper.readValue(userString, UserEntity.class);
        System.out.println(user);

        System.out.println("=============");

        String newString = objectMapper.writeValueAsString(user);
        System.out.println(newString);
    }
}

class UserEntity implements Serializable {
    
    
    private String phone;
    private String pwd;
}

那什么叫做序列化和反序列化呢?其实就是从对象到字符串,那么称之为正向转换,也就是序列化,反之则是反序列化。

  • 序列化: 将 字符串 数据类型转换为 class 这种类型
  • 反序列化: 将 class 数据类型转换为 字符串 这种类型

序列化 这个名称初听让人很懵逼,什么玩意儿啊这是?所以为什么要取这么个名字?

数据传输后都需要存储在磁盘上,对于磁盘来说, 数据需要按照顺序和位置存储在指定位置,下次才能再读取出来。所以对于数据而言,需要提前将其顺序排列好,这样便于存入磁盘中。

所以序列化的含义就是有序的队列,化就是转化,将数据转化为有顺序的队列。 一切为了存储,一切为了存储,一切为了存储。

技术从国外传入时,译者起了这么个名字流传甚广,从此成为定例。其实也可以叫做转换,转化,变化都可以。


ObjectMapper 内部方法

其实剩下的内容很简单了,就是看看 ObjectMapper 为我们提供了哪些方法,看看怎么使用它们。方法有很多,简单列举几个。

  1. 从 JSON 文件读取 Java class 对象
ObjectMapper objectMapper = new ObjectMapper();
File file = new File("data/car.json");
Car car = objectMapper.readValue(file, Car.class);
  1. 从 JSON 字符串读取 Java Map 对象

String jsonObject = "{\"brand\":\"ford\", \"doors\":5}";
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> jsonMap = objectMapper.readValue(jsonObject, new TypeReference<Map<String,Object>>(){
    
    });
  1. 从 JSON 数组字符串中读取 Java Array 对象
String jsonArray = "[{\"brand\":\"ford\"}, {\"brand\":\"Fiat\"}]";
ObjectMapper objectMapper = new ObjectMapper();
Car[] cars2 = objectMapper.readValue(jsonArray, Car[].class);

还有很多方法,大同小异,你只需要知道 jackson 提供了 从多种数据类型以及文件中读取数据并与 java 对象相互转换得方法,需要得时候查阅文档


常见的若干特殊情况:

  1. 有时要读取的 JSON 数据的属性要多于你的 Java 对象的属性, 默认情况下 Jackson 这时会抛出 UnrecognizedPropertyException 异常,此时需要设置一项
  objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
  1. 有时要读取的 JSON 数据的属性值为 null,而 ObjectMapper 会默认忽略那些值为 null 的字段。为了能够知道哪些数据为空,你可以设置抛出异常
  objectMapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, true);
  1. 定制反序列化器。定制就是最终极的序列化解决办法了,几乎可以解决所有问题。
    而自定义反序列化器,主要就 2 个步骤,其中的核心是实现接口 StdDeserializer

比如,我现在要序列化的数据是:

{
    
    
  "phone": "13911112222",
  "pwd": "123456",
  "role": {
    
    
    "roleId": "666"
  }
}

我希望将数据序列化为这两个实体:

@Data
public class UserEntity implements Serializable {
    
    
    private String phone;
    private String pwd;
    public UserEntity(String phone,String password,RoleEntity role) {
    
    
        this.phone = phone;
        this.password = password;
        this.role = role;
    }

}

@Data
class RoleEntity implements Serializable {
    
    
    private Integer roleId;
    private String roleName;
}

我希望在序列化 User 时,自动创建其角色名称为:freedom。那么整个自定义序列化的流程是:

@Test
public void testObjectMapperDefine() throws JsonProcessingException {
    
    

    // 1. 创建 objectMapper 对象
    ObjectMapper objectMapper = new ObjectMapper();
    // 2. 创建 module 对象 (SimpleModule 类似于可安装到 ObjectMapper的插件)
    SimpleModule module = new SimpleModule();
    // 3. 对 module 这个插件先进行设置。设置此插件将要作用于 UserEntity,并指定目标的序列化方法(序列化方法稍后由我们自定义)
    module.addDeserializer(UserEntity.class, new DefineMySerialHandle());
    // 4. module 插件一切搞定之后,注册到 objectMapper 中
    objectMapper.registerModule(module);

    // 现在当我们使用 objectMapper 来对数据进行序列化/反序列化时,符合 UserEntity 这个类的数据就会被自定义的序列化方法操作
    // 验证执行
    String userString = "{\"phone\": \"13911112222\", \"pwd\" : \"123456\",\"role\":{\"roleId\":\"666\"}}";
    UserEntity user = objectMapper.readValue(userString, UserEntity.class);

    // 反序列化后得到的User实体,可以看到它的角色名,也就是 roleName 属性被设置为了 freedom
    System.out.println(user);
}

自定义反序列化方法:

// 泛型指定 序列化目标
public class DefineMySerialHandle extends StdDeserializer<UserEntity> {
    
    
    public DefineMySerialHandle() {
    
    
        this(null);
    }
    protected DefineMySerialHandle(Class<?> vc) {
    
    
        super(vc);
    }
    @Override
    public UserEntity deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JacksonException {
    
    

        JsonNode node = jsonParser.getCodec().readTree(jsonParser);
        String phone = node.get("phone").asText();
        String pwd = node.get("pwd").asText();

        RoleEntity role = new RoleEntity();
        role.setRoleName("freedom");

        // 反序列化,首要核心目的是 得到序列化目标 UserEntity,因此必须返回 UserEntity 类型
        // 而将实体Role的字段roleName设置 freedom 是 附带的随手操作
        return new UserEntity(phone, pwd, role);
    }
}
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Item.class, new ItemDeserializer());
mapper.registerModule(module);

Item readValue = mapper.readValue(json, Item.class);

到这里已经足够应付工作中的几乎所有场景了,你唯一需要做的是,将 ObjectMapper 提供的 API 自己挑 10 个调用测试一下,足以用一辈子.

使用场景 - 全局异常

在 springboot 项目中处理全局异常时,通常会使用到 Jasckson.此处其实就是将 Java 中的 Map 类型数据转换为 Json 字符串。

package com.mock.water.core.exception;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.HashMap;
import java.util.Map;

@RestControllerAdvice
public class GlobalExceptionHandler {
    
    

    @Autowired
    private ObjectMapper objectMapper;

    @ExceptionHandler(value= {
    
    MethodArgumentNotValidException.class , BindException.class})
    public String onException(Exception e) throws JsonProcessingException {
    
    
        BindingResult bindingResult = null;
        if (e instanceof MethodArgumentNotValidException) {
    
    
            bindingResult = ((MethodArgumentNotValidException)e).getBindingResult();
        } else if (e instanceof BindException) {
    
    
            bindingResult = ((BindException)e).getBindingResult();
        }
        Map<String,String> errorMap = new HashMap<>(16);
        assert bindingResult != null;
        bindingResult.getFieldErrors().forEach((fieldError)->
                errorMap.put(fieldError.getField(),fieldError.getDefaultMessage())
        );
        return objectMapper.writeValueAsString(errorMap);
    }
}

Tips,思考一下,在这个示例中使用 @Autowired 注入 ObjectMapper 对象来使用,如果要自定义一个处理模块,如何添加到里面呢?

使用场景 - 设置时区

Jackson 对时间进行格式化时,默认时区为UTC

JVM 在运行时采用的是操作系统时区,部暑在Linux系统中后,默认时区是 GMT+8 .当然对于我们本地开发时是UTC.你可以通过打开 系统 - 日期与时间来查看.

所以需要让他们统一,这样在格式化时间时才正常。在 application.yml 中配置如下:

spring:
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
    default-property-inclusion: NON_NULL

拓展阅读


------ 如果文章对你有用,感谢右上角 >>>点赞 | 收藏 <<<

猜你喜欢

转载自blog.csdn.net/win7583362/article/details/127370142