Jackson中那些你需要掌握的知识点

简介: 使用json进行数据交互可以说是非常常见的常见,在java侧,常用的json解析框架也不少,比如gson, fastjson以及spring mvc中默认使用的jackson;本文将主要介绍一下jackson的基本使用姿势,以及在日常开发中,你应该了解的知识点,比如常见的

image.png


使用json进行数据交互可以说是非常常见的常见,在java侧,常用的json解析框架也不少,比如gson, fastjson以及spring mvc中默认使用的jackson;本文将主要介绍一下jackson的基本使用姿势,以及在日常开发中,你应该了解的知识点,比如常见的


  • 普通对象转json字符串
  • json字符串转POJO,转Map/List
  • 泛型支持
  • 驼峰/下划线互转,自定义映射关系
  • bean的遵循规范
  • 序列化/反序列化异常的场景


1. 项目依赖



使用maven来构建项目,需要使用Jackson进行序列化操作,核心引入下面的包

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


2. 基本使用姿势



在Jackson中,若希望实现序列化/反序列化,都离不开ObjectMapper

比如将对象转Json String

public static <T> String encode(T obj) {
    try {
        return new ObjectMapper().writeValueAsString(obj);
    } catch (Exception e) {
        throw new UnsupportedOperationException(e);
    }
}
复制代码


反序列化

public static <T> T decode(String str, Class<T> clz) {
    try {
        return new ObjectMapper().readValue(str, clz);
    } catch (Exception e) {
        throw new UnsupportedOperationException(e);
    }
}
复制代码


注意

  • jackson与gson/fastjson的一个显著区别在于它的序列化/反序列化有声明异常,使用时需要声明或者主动catch(这一点感觉不太友好)
  • 其次,不推荐每次都创建一个ObjectMapper对象,可以考虑复用


3. 泛型反序列化



对于泛型的反序列化,直接使用上面,传入一个class对象,并不能很好的工作,和Gson/FastJson一样,Jackson也支持根据Type来返序列化


public static <T> T decode(String str, Type type) {
    try {
        return objectMapper.readValue(str, objectMapper.getTypeFactory().constructType(type));
    } catch (Exception e) {
        throw new UnsupportedOperationException(e);
    }
}
复制代码


重点关注上面的传参,通过objectMapper.getTypeFactory().constructType(type)来创建需要的JavaType对象


一个demo使用姿势如下

GenericBean<Map> gbean2 = JacksonUtil.decode(str, new com.fasterxml.jackson.core.type
      .TypeReference<GenericBean<Map>>() {}.getType());
System.out.println(gbean2);
复制代码


4. 转Map/List



转普通的Map/List没有什么特殊的

public static Map toMap(String str) {
    try {
        return objectMapper.readValue(str, Map.class);
    } catch (JsonProcessingException e) {
        throw new UnsupportedOperationException(e);
    }
}
public static List toList(String str) {
    try {
        return objectMapper.readValue(str, List.class);
    } catch (JsonProcessingException e) {
        throw new UnsupportedOperationException(e);
    }
}
复制代码


5. JsonNode



JsonNode为Jackson定义的节点对象,有些类似Gson的JsonObject/JsonArray 和 FastJson的JSONObject/JSONArray,使用它可以更友好的操作json对象(当然更推荐的是直接转JAVA bean)


public static JsonNode toObj(String str) {
    try {
        return objectMapper.readTree(str);
    } catch (JsonProcessingException e) {
        throw new UnsupportedOperationException(e);
    }
}
复制代码


使用demo如下

String str = "{\"userId\":12,\"userName\":\"yh\",\"userMoney\":12.3,\"userSkills\":[\"1\",\"2\",\"3\"],\"extra\":{\"a\":\"123\",\"b\":345,\"c\":[\"1\",\"2\",\"3\"],\"d\":35.1},\"hello\":\"你好\"}";
JsonNode bean = JacksonUtil.toObj(str);
int userId = bean.get("userId").asInt();
复制代码


6. 驼峰与下划线



常见的一个case,json字符串key为下划线,Java bean为驼峰命名,针对这种场景,jackson可以很方便的支持

/**
 * 驼峰转下换线
 *
 * @param obj
 * @return
 */
public static String toUnderStr(Object obj) {
    ObjectMapper objectMapper = new ObjectMapper();
    // 驼峰转下划线
    objectMapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
    try {
        return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
    } catch (JsonProcessingException e) {
        throw new UnsupportedOperationException(e);
    }
}
/**
 * 下划线格式json串,转驼峰格式的Java bean
 */ 
public static <T> T fromUnderStr(String str, Class<T> clz) {
    ObjectMapper objectMapper = new ObjectMapper();
    // 驼峰转下划线
    objectMapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
    try {
        return objectMapper.readValue(str, clz);
    } catch (JsonProcessingException e) {
        throw new UnsupportedOperationException(e);
    }
}
复制代码


从上面的代码也可以看出,驼峰与下划线的互转支持,主要是通过设置

PropertyNamingStrategies来实现的,在jackson中,支持下面几种配置


  • LOWER_CAMEL_CASE
  • UPPER_CAMEL_CASE
  • SNAKE_CASE
  • LOWER_CASE
  • KEBAB_CASE
  • LOWER_DOT_CASE


使用上面这种方式适用于全局的下划线与驼峰的转换方式,如果我只希望针对单独某个类进行这样的设置呢?


可以借助注解@JsonNaming来实现


@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class SimpleBean implements Serializable {
    private static final long serialVersionUID = -9111747337710917591L;
    private Integer userId;
    private String userName;
    private double userMoney;
}
复制代码


上面这个case中,如果我们将SimpleBean对象序列化为json串,即便调用的是前面最基础的使用姿势 new ObjectMapper().writeValueAsString(simpleBean),输出的也是下划线格式的json串;同理反序列化时,也是将下划线转为驼峰


7. 字段别名



上面介绍的是驼峰与下划线命名方式,当然也会有一些其他特殊的场景,针对某个字段进行别名设置,可以通过注解@JsonProperties来标注


@JsonProperty("user")
private String userName;
复制代码

上面这个表示序列化为json字符串时,userName对应的key为user


8. 字段忽略


在序列化时,难免会遇到某些字段不进行序列化/反序列化的场景,这里有两种常用的方式


8.1 @JsonIgnore注解


直接在希望忽略的字段上添加注解@JsonIgnore即可,如

@Data
public class SimpleBean implements Serializable {
    private static final long serialVersionUID = -9111747337710917591L;
    @JsonIgnore
    private SimpleBean self;
}
复制代码


8.2 transient关键字


除了使用上面的注解之外,也可以使用jdk原生提供的关键字transient来声明需要忽略的字段


private transient SimpleBean self;
复制代码


重点注意:


  • 在jackson中,默认的场景下,即便字段上修饰有transient关键字,也不会忽略还需要如下处理
objectMapper = new ObjectMapper();
// 忽略 transient 关键字的配置
// case1
objectMapper.configure(MapperFeature.PROPAGATE_TRANSIENT_MARKER, true);
// case2
objectMapper.setVisibility(objectMapper.getSerializationConfig()
        .getDefaultVisibilityChecker()
        .withFieldVisibility(JsonAutoDetect.Visibility.ANY)
        .withGetterVisibility(JsonAutoDetect.Visibility.NONE)
        .withIsGetterVisibility(JsonAutoDetect.Visibility.NONE));
复制代码


上面两种方式,都可以实现忽略transient关键字修饰的对象序列化


9. Java Bean约定



9.1 get/set方法必须有


Java Bean的get/set方法必须存在,否则额序列化与反序列化只会处理public修饰的成员

public class SimpleBean {
  private String name;
  private Long userId;
  public String desc = "hello world";
  public SimpleBean() {
      name = "yhh";
      userId = 10L;
  }
}
复制代码


序列化后输出为 {"desc":"hello world"}; 反序列化也不会更新 name, userId


特别的,当Java Bean对象,所有的成员都是private,又没有get方法时,在序列化时,会抛异常,提示信息如下


No serializer found for class com.git.hui.spring.json.bean.SimpleBean and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)
复制代码


9.2 无参构造函数必须有


如果java bean没有默认无参构造方法,那么在反序列化时,会抛出异常,无法实例化

一个如下的异常提示信息


Cannot construct instance of `com.git.hui.spring.json.bean.SimpleBean` (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator)
复制代码


10. Json串存在Bean未定义字段忽略设置



默认的使用姿势下,若json串中存在一个bean未定义的kv,会抛异常,一个示例如下

Unrecognized field "xxx" (class com.git.hui.spring.json.bean.GenericBean), not marked as ignorable
复制代码


如果希望忽略这种场景,那么就需要禁用FAIL_ON_UNKNOWN_PROPERTIES配置

new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
    .readValue(str, Xxx.class)
复制代码


扩展知识点

  • 对于Spring MVC而言,默认使用的是Jackson序列化框架,如果我们定义接收参数为json串,那么当前端传参多了一个未定义的字段,会直接抛异常么?


11. 序列化输出时忽略null



默认场景下,将一个bean对象序列化为json串,即便成员变量为null,也会输出,如下

{
  "userId" : null,
  "userName" : null,
  "userMoney" : 0.0,
  "userSkills" : null,
  "extra" : null
}
复制代码


这种case某些场景下是合适的,比如生成接口文档示例时,更关心的是参数说明,即便为null,也是希望有这个;但是另外一些场景下则希望忽略,毕竟可以节省对象大小

需要忽略null字段时,可以如下设置


new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL)
    .writerWithDefaultPrettyPrinter().writeValueAsString(xxx)
复制代码


关键点就是配置 setSerializationInclusion(JsonInclude.Include.NON_NULL)


12. key为null场景兼容



对于普通的Java bean而言,不存在key为null的场景,但是如果是将一个Map对象,输出为json串时,那么就可能出现这种场景了,如


Map<String, String> map = new HashMap<>();
map.put(null, "123");
new ObjectMapper().writeValueAsString(map);
复制代码


上面这个执行,直接抛异常

Null key for a Map not allowed in JSON (use a converting NullKeySerializer?)
复制代码


如果希望兼容这个场景,则可以如下处理

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.getSerializerProvider().setNullKeySerializer(new JsonSerializer<Object>() {
    @Override
    public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        gen.writeFieldName("null");
    }
});
复制代码


上面这个做法,就是将key为null的,以null字符串来替代


说明


  • 既然key可能为null,当然也为其他类型,但是在序列化输出时,会转String

如下面这个case

Map map = new HashMap();
map.put(new ArrayList<>(), 123);
new ObjectMapper().writeValueAsString(map);
复制代码


输出的字符串为

{"[]":123}
复制代码


13 其他



以上的知识点,基本上可以覆盖我们日常在使用Jackson进行序列化和反序列化中95%的场景,至于其他的比如自定义Name策略,反序列化的默认值类型,类型转换,json注释的支持与否等相对少见的姿势,看后续是否有空补上



相关文章
|
8月前
|
JSON 安全 Java
jackson学习之二:jackson-core
了解jackson最底层的功能逻辑
130 0
jackson学习之二:jackson-core
|
8月前
|
JSON Java 开发工具
jackson学习之八:常用方法注解
熟悉和使用jackson常用的方法注解
jackson学习之八:常用方法注解
|
3天前
|
JSON fastjson Java
fastjson是什么东西,怎么用?
fastjson是什么东西,怎么用?
|
7月前
|
存储 Java
【面试题精讲】ProtoStuff
【面试题精讲】ProtoStuff
|
8月前
|
XML Java 编译器
Lombok 天天用,却不知道它的原理是什么?
Lombok 天天用,却不知道它的原理是什么?
50 1
|
8月前
|
Java 程序员 网络安全
jackson学习之五:JsonInclude注解
JsonInclude注解重点说明
134 2
jackson学习之五:JsonInclude注解
|
8月前
|
JSON Java 网络安全
jackson学习之六:常用类注解
熟悉和使用Jackson常用的类注解
jackson学习之六:常用类注解
|
8月前
|
XML JSON Java
jackson学习之一:基本信息
对jackson有个基本了解
jackson学习之一:基本信息
|
XML 缓存 安全
Java初级-Spring常见面试题总结
Java初级-Spring常见面试题总结