Jackson 2.x 系列【4】对象映射器 ObjectMapper

有道无术,术尚可求,有术无道,止于术。

本系列 Jackson 版本 2.17.0

源码地址:https://gitee.com/pearl-organization/study-jackson-demo

1. 概述

在前两篇文档中,我们学习了JsonGeneratorJsonParser的简单用法,实际开发中很少用到它们,因为它们属于低级API,自由度高但用起来比较繁琐。 我们使用最多的还是ObjectMapper,它是jackson-databind数据绑定模块提供面向用户的高级API

官方注释中说明了主要的功能特性

  • 提供了从POJOJSON树模型读取和写入JSON的功能,并支持互相转换
  • 支持高度自定义,以使用不同样式的JSON内容
  • 支持更高级的对象概念,如多态泛型和对象标识
  • 充当了更高级的ObjectReaderObjectWriter类的工厂

ObjectMapper类关系图如下所示:

其中TreeCodecObjectCodec声明了树模型、对象绑定的读写操作,ObjectMapper还有一个子类JsonMapper,专用于处理JSON格式。

2. 案例演示

首先需要引入jackson-databind数据绑定模块:

xml 复制代码
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.17.0</version>
        </dependency>

2.1 创建对象

ObjectMapper提供了多个构造函数:

一般使用默认的构造函数即可,其他的方式后面会单独介绍:

java 复制代码
        ObjectMapper objectMapper = new ObjectMapper();

2.2 写入

ObjectMapper提供了多个写入方法,支持将对象以JSON格式写入到文件、输出流等对象中,以及返回字符串、字节数组:

POJO对象转换为JSON字符串(序列化)示例如下:

java 复制代码
        // POJO->JSON
        User user = new User();
        user.setId(1767798780627279873L);
        user.setName("坤坤");
        user.setAge(18);
        String json = objectMapper.writeValueAsString(user);
        System.out.println(json);

控制台输出如下:

json 复制代码
{"id":1767798780627279873,"name":"坤坤","age":18,"org":null,"roleList":null}

写入到文件示例如下:

java 复制代码
        // 写入到文件
        File file = new File("E:\\TD\\pearl\\study-jackson-demo\\jackson-core-demo\\src\\main\\java\\com\\pearl\\jacksoncore\\demo\\file\\user.json");
        objectMapper.writeValue(file,user);

2.3 读取

ObjectMapper也提供了多个读取方法,支持从多种地方读取JSON内容并转换为POJO对象:

从文件中读取JSON并转换为POJO示例:

java 复制代码
        // 文件读取JSON -> POJO
        User readUserByFile = objectMapper.readValue(file, User.class);
        System.out.println(readUserByFile);

从字符串中读取JSON并转换为POJO(反序列化)示例:

java 复制代码
        String jsonStr="{\"id\":1767798780627279873,\"name\":\"坤坤\",\"age\":18,\"org\":null,\"roleList\":null}";
        User userPojo = objectMapper.readValue(jsonStr, User.class);
        System.out.println(userPojo);

此外ObjectMapper实现了很多将JSON读取为树模型的方法:

树模型 使用树状结构来呈现对象,例如用户对象的树模型示意图:

JSON不太好转换为某个标准的对象时,可以直接转换为统一的树模型,示例如下:

java 复制代码
        String userJsonStr = "{\"id\":1767798780627279873,\"name\":\"坤坤\",\"age\":18,\"org\":null,\"roleList\":[{\"id\":null,\"roleName\":\"管理员\",\"roleCode\":\"ROLE_ADMIN\"},{\"id\":null,\"roleName\":\"普通用户\",\"roleCode\":\"ROLE_USER\"}]}";
        JsonNode jsonNode = objectMapper.readTree(userJsonStr);
        long userId = jsonNode.get("id").asLong();// 获取id 节点对应的值并转为 Long
        String userName = jsonNode.get("name").asText();// 获取 name 节点对应的值并转为 String
        String roleName = jsonNode.get("roleList").get(0).get("roleName").asText(); //  获取 roleList 节点对应的值,再获取第一个元素,再获取roleName 的值并转为 String

3. 泛型擦除

相信大家对JAVA中的泛型已经十分了解,其本质是参数化类型,在后台返回给前端数据时,一般都会使用一个统一的响应结果封装类,并使用泛型标识返回数据的类型:

java 复制代码
public class R<T> {

    /**
     * 状态码
     */
    private Integer code;

    /**
     * 返回信息
     */
    private String msg;

    /**
     * 数据
     */
    private T data;

    public static <T> R<T> response(Integer code, String msg, T data) {
        R<T> result = new R<>();
        result.setCode(code);
        result.setMsg(msg);
        result.setData(data);
        return result;
    }
    // 省略 getter/setter......
}    

调用示例:

java 复制代码
R<User> response = R.response(200, "操作成功", user);

但是在反序列化(将JSON转为POJO)并获取泛型指定的对象时,示例代码:

java 复制代码
        String jsonStr = "{\"code\":200,\"msg\":\"操作成功\",\"data\":{\"id\":1767798780627279873,\"name\":\"坤坤\",\"age\":18,\"org\":null,\"roleList\":[{\"id\":null,\"roleName\":\"管理员\",\"roleCode\":\"ROLE_ADMIN\"},{\"id\":null,\"roleName\":\"普通用户\",\"roleCode\":\"ROLE_USER\"}]}}";
        R<User> response= objectMapper.readValue(jsonStr, R.class);
        User data = response.getData();

这时会引发ClassCastException类型转换异常:

java 复制代码
Exception in thread "main" java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to class com.pearl.jacksoncore.demo.pojo.User (java.util.LinkedHashMap is in module java.base of loader 'bootstrap'; com.pearl.jacksoncore.demo.pojo.User is in unnamed module of loader 'app')
	at com.pearl.jacksoncore.demo.databind.ObjectMapperTest.main(ObjectMapperTest.java:89)

通过Debug可以看到,虽然我们指定了接收对象的泛型为User,但是实际的data却是LinkedHashMap类型:

这是由于JAVA中的泛型擦除机制导致的,为了兼容老版本的Java和为了性能考虑,在编译期会进行类型擦除,在运行期间JVM并不知道泛型的存在,在对JSON字符串进行解析时,JVM自然也不知道需要将其解析为哪种类型,则默认解析为LinkedHashMap,所以导致ClassCastException类型转换异常。

针对JAVA泛型擦除问题,Jackson提供了TypeReference<T>抽象类指定转换时的泛型类型,这样在反序列化时,也就知道是什么类型了,使用示例如下:

java 复制代码
R<User> response = objectMapper.readValue(jsonStr, new TypeReference<R<User>>() { });
相关推荐
开发游戏的老王20 小时前
虚幻引擎虚拟制片入门教程 之 模型资源的导入
java·游戏引擎·虚幻
编啊编程啊程20 小时前
【004】生菜阅读平台
java·spring boot·spring cloud·dubbo·nio
Craaaayon20 小时前
【数据结构】二叉树-图解广度优先搜索
java·数据结构·后端·算法·宽度优先
岁岁岁平安21 小时前
Java+SpringBoot+Dubbo+Nacos快速入门
java·spring boot·nacos·rpc·dubbo
学习编程的Kitty21 小时前
算法——位运算
java·前端·算法
用户904706683571 天前
如何使用 Spring MVC 实现 RESTful API 接口
java·后端
刘某某.1 天前
数组和小于等于k的最长子数组长度b
java·数据结构·算法
程序员飞哥1 天前
真正使用的超时关单策略是什么?
java·后端·面试
用户904706683571 天前
SpringBoot 多环境配置与启动 banner 修改
java·后端
小old弟1 天前
后端三层架构
java·后端