6. 二十不惑,ObjectMapper使用也不再迷惑(下)

简介: 6. 二十不惑,ObjectMapper使用也不再迷惑(下)

读(反序列化)


提供readValue()系列方法用于读数据(一般读字符串类型),也就是我们常说的反序列化


image.png


  • readValue(String content, Class<T> valueType):读为指定class类型的对象,此方法最常用
  • readValue(String content, TypeReference<T> valueTypeRef):T表示泛型类型,如List<T>这种类型,一般用于集合/Map的反序列化
  • readValue(String content, JavaType valueType):Jackson内置的JavaType类型,后再详解(使用并不多)


@Test
public void test4() throws JsonProcessingException {
    ObjectMapper objectMapper = new ObjectMapper();
    System.out.println("----------读简单类型----------");
    System.out.println(objectMapper.readValue("18", Integer.class));
    // 抛错:JsonParseException  单独的一个串,解析会抛错
    // System.out.println(objectMapper.readValue("YourBatman", String.class));
    System.out.println("----------读集合类型----------");
    System.out.println(objectMapper.readValue("[1,2,3]", List.class));
    System.out.println(objectMapper.readValue("{\"zhName\":\"A哥\",\"enName\":\"YourBatman\"}", Map.class));
    System.out.println("----------读POJO----------");
    System.out.println(objectMapper.readValue("{\"name\":\"A哥\",\"age\":18}", Person.class));
}


运行程序,输出:


----------读简单类型----------
18
----------读集合类型----------
[1, 2, 3]
{zhName=A哥, enName=YourBatman}
----------读POJO----------
Person(name=A哥, age=18)


不同于序列化,可以把“所有”写成为一个字符串。反序列化场景有它特殊的地方,比如例子中所示:不能反序列化一个“单纯的”字符串。


泛型擦除问题


从例举出来的三个read读方法中,就应该觉得事情还没完,比如这个带泛型的case:


@Test
public void test5() throws JsonProcessingException {
    ObjectMapper objectMapper = new ObjectMapper();
    System.out.println("----------读集合类型----------");
    List<Long> list = objectMapper.readValue("[1,2,3]", List.class);
    Long id = list.get(0);
    System.out.println(id);
}

运行程序,抛错:


----------读集合类型----------
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Long
  at cn.yourbatman.jackson.core.ObjectMapperDemo.test5(ObjectMapperDemo.java:100)
  ...


异常栈里指出:Long id = list.get(0);这一句出现了类型转换异常,这便是问题原因所在:泛型擦除,参考图示如下(明明泛型类型是Long,但实际装的是Integer类型):



image.png



对这种问题,你可能会“动脑筋”思考:写成[1L,2L,3L]这样行不行。思想很活跃,奈何现实依旧残酷,运行抛错:


com.fasterxml.jackson.core.JsonParseException: Unexpected character ('L' (code 76)): was expecting comma to separate Array entries
 at [Source: (String)"[1L,2L,3L]"; line: 1, column: 4]
 ...


这是典型的泛型擦除问题。该问题只可能出现在读(反序列化)上,不能出现在写上。那么这种问题怎么破?


在解决此问题之前,我们得先对Java中的泛型擦除有所了解,至少知道如下两点结论


  1. Java 在编译时会在字节码里指令集之外的地方保留部分泛型信息
  2. 泛型接口、类、方法定义上的所有泛型、成员变量声明处的泛型都会被保留类型信息,其它地方的泛型信息都会被擦除


此问题在开发过程中非常高频,有了此理论作为支撑,A哥提供两种可以解决本问题的方案供以参考:

方案一:利用成员变量保留泛型


理论依据:成员变量的泛型类型不会被擦除


@Test
public void test6() throws JsonProcessingException {
    ObjectMapper objectMapper = new ObjectMapper();
    System.out.println("----------读集合类型----------");
    Data data = objectMapper.readValue("{\"ids\" : [1,2,3]}", Data.class);
    Long id = data.getIds().get(0);
    System.out.println(id);
}
@lombok.Data
private static class Data {
    private List<Long> ids;
}


运行程序,一切正常:

----------读集合类型----------
1


方案二:使用官方推荐的TypeReference<T>


官方早早就为我们考虑好了这类泛型擦除的问题,所以它提供了TypeReference<T>方便我们把泛型类型保留下来,使用起来是非常的方便的:


@Test
public void test7() throws JsonProcessingException {
    ObjectMapper objectMapper = new ObjectMapper();
    System.out.println("----------读集合类型----------");
    List<Long> ids = objectMapper.readValue("[1,2,3]", new TypeReference<List<Long>>() {
    });
    Long id = ids.get(0);
    System.out.println(id);
}


运行程序,一切正常:

----------读集合类型----------
1


本方案的理论依据是:泛型接口/类上的泛型类型不会被擦除。


对于泛型擦除情况,解决思路是hold住泛型类型,这样反序列化的时候才不会抓瞎。但凡只要一抓瞎,Jackson就木有办法只能采用通用/默认类型去装载喽。


加餐


自2.10版本起,给ObjectMapper提供了一个子类:JsonMapper,使得语义更加明确,专门用于处理JSON格式。


严格意义上讲,ObjectMapper不局限于处理JSON格式,比如后面会讲到的它的另外一个子类YAMLMapper用于对Yaml格式的支持(需额外导包,后面见~)


另外,由于构建一个ObjectMapper实例属于高频动作,因此Jackson也顺应潮流的提供了MapperBuilder构建器(2.10版本起)。我们可以通过此构建起很容易的得到一个ObjectMapper(以JsonMapper为例)实例来使用:


@Test
public void test8() throws JsonProcessingException {
    JsonMapper jsonMapper = JsonMapper.builder()
            .configure(JsonReadFeature.ALLOW_SINGLE_QUOTES, true)
            .build();
    Person person = jsonMapper.readValue("{'name':  'YourBatman', 'age': 18}", Person.class);
    System.out.println(person);
}


运行程序,正常输出:

Person(name=YourBatman, age=18)


总结


本文内容很轻松,讲述了ObjectMapper的日常使用,使用它进行读/写,完成日常功能。


对于写来说比较简单,一个writeValueAsString(obj)方法走天下;但对于读来说,除了使用readValue(String content, Class<T> valueType)自动完成数据绑定外,需要特别注意泛型擦除问题:若反序列化成为一个集合类型(Collection or Map),泛型会被擦除,此时你应该使用readValue(String content, TypeReference<T> valueTypeRef)方法代替。


小贴士:若你在工程中遇到objectMapper.readValue(xxx, List.class)这种代码,那肯定是有安全隐患的(但不一定报错)

相关文章
|
5月前
|
存储 Java
惊呆了!这些Java List竟然藏着这么多秘密!你get到了吗?
【6月更文挑战第17天】Java中的ArrayList在添加元素时自动扩容,容量翻倍以适应增长;LinkedList则利用双向链表结构提供高效头尾操作。迭代List时,并发修改会导致`ConcurrentModificationException`,需用Iterator或并发集合如CopyOnWriteArrayList。了解这些秘密能优化性能并避免异常。
23 0
|
5月前
|
安全 Java 数据安全/隐私保护
揭秘 Java 的“心灵封印术”:如何巧妙隐藏对象的小秘密?
【6月更文挑战第15天】Java的封装是面向对象的核心,它隐藏对象内部细节,只暴露必要的接口。比如`Student`类中,私有属性`name`和`age`通过公共方法访问,保证数据安全。同样,`BankAccount`类封装`balance`,仅允许通过`deposit`、`withdraw`和`getBalance`操作,防止数据误改。封装使代码更健壮、易维护,是编程的强力工具。
60 0
|
5月前
|
Java
深入 Java 面向对象:类的定义,竟然藏着这么多门道!
【6月更文挑战第15天】Java中的类定义是OOP的基础,它封装属性(如Student的name和age)和行为(如study())。简单的类仅触及表面,而复杂的类可模拟真实世界对象的多样性和交互。类还可通过继承扩展,如Student从Person派生,增加特有属性和行为。接口和抽象类等概念进一步增强了灵活性和可扩展性。类定义的深度和广度是构建高效、可维护代码的关键。
33 0
|
前端开发 Java 编译器
Java的第十六篇文章——枚举、反射和注解(后期再学一遍)
Java的第十六篇文章——枚举、反射和注解(后期再学一遍)
|
存储 安全 算法
《我要进大厂》- Java基础夺命连环14问,你能坚持到第几问?(Object类 | String类)(一)
《我要进大厂》- Java基础夺命连环14问,你能坚持到第几问?(Object类 | String类)
《我要进大厂》- Java基础夺命连环14问,你能坚持到第几问?(Object类 | String类)(一)
|
存储 Java 数据安全/隐私保护
《我要进大厂》- Java基础夺命连环10问,你能坚持到第几问?(面向对象基础篇)
《我要进大厂》- Java基础夺命连环10问,你能坚持到第几问?(面向对象基础篇)
《我要进大厂》- Java基础夺命连环10问,你能坚持到第几问?(面向对象基础篇)
|
Java 编译器
《我要进大厂》- Java基础夺命连环14问,你能坚持到第几问?(Object类 | String类)(二)
《我要进大厂》- Java基础夺命连环14问,你能坚持到第几问?(Object类 | String类)
《我要进大厂》- Java基础夺命连环14问,你能坚持到第几问?(Object类 | String类)(二)
|
安全 Java 编译器
《我要进大厂》- Java基础夺命连环10问,你能坚持到第几问?(异常 | 泛型)
《我要进大厂》- Java基础夺命连环10问,你能坚持到第几问?(异常 | 泛型)
《我要进大厂》- Java基础夺命连环10问,你能坚持到第几问?(异常 | 泛型)
|
Rust Oracle Java
《我要进大厂》- Java基础夺命连环18问,你能坚持到第几问?(基础概念 | 基本语法 | 基本数据类型)(一)
《我要进大厂》- Java基础夺命连环18问,你能坚持到第几问?(基础概念 | 基本语法 | 基本数据类型)
《我要进大厂》- Java基础夺命连环18问,你能坚持到第几问?(基础概念 | 基本语法 | 基本数据类型)(一)
|
IDE Java 编译器
《我要进大厂》- Java基础夺命连环18问,你能坚持到第几问?(基础概念 | 基本语法 | 基本数据类型)(二)
《我要进大厂》- Java基础夺命连环18问,你能坚持到第几问?(基础概念 | 基本语法 | 基本数据类型)
《我要进大厂》- Java基础夺命连环18问,你能坚持到第几问?(基础概念 | 基本语法 | 基本数据类型)(二)