环境
java:1.8+
jackson-databind:2.9.1
前言
最近做需求时,解析的json比较奇特,里面嵌套多层json,并且这些嵌套的json里面会存在单引号,利用Java存进入数据库后,又会存在转义符,这样反序列化时,就会报错,解析失败。
刚开始,我使用的是fastJson来反序列化,但是在单引号、转义符方面,支持的真的不够好。
后面改为使用jackson,它对转义符、单引号、null、空串等,有非常好的解决方案。
引入依赖
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.1</version>
</dependency>
使用方法
创建objectMapper
第一步,我们需要创建一个ObjectMapper
对象,通过它我们可以序列化或者反序列化。
ObjectMapper objectMapper = new ObjectMapper();
序列化和反序列化的简单使用
如果是简单的解析:
User user = new User();
user.setName("rongK");
user.setAge(28);
user.setCreateTime(new Timestamp(new Date().getTime()));
//序列化
String s = objectMapper.writeValueAsString(user);
System.out.println("对象序列化:" + s);// 对象序列化:{"name":"rongK","age":28,"createTime":1598081001761}
//反序列化
User u = objectMapper.readValue(s, User.class);
System.out.println("对象反序列化:" + u);// 对象反序列化:User(name=rongK, age=28, createTime=2020-08-22 15:23:21.761)
上面的例子只是简单的使用,但是会存在这下列的一些情况:
问题① 假设反序列化的json
里面有个字段,在POJO
类中没有,会反序列失败
问题② 反序列化的json
格式存在一定问题:
如果是以下情况,也会反序列化失败:
{
//User是个POJO,并不是个字符串
//默认情况下,反序列化会失败
"User": ""
}
问题③反序列化json
里面存在单引号的情况
{"expression":"${detail.putDetailJsonParam(execution,ruleService.mergeFilterKey(null,null,jsonUtils.parseObject('{"orderId":'.concat(orderId).concat('}')),'preposition','sgy',jsonUtils.parseJson2Array('["info","attributeJSON"]','java.lang.String')).infoJson)}"
可以看出value
是个字符串,但是该字符串里面存在单引号
和双引号
的嵌套使用。
问题④ 如果存在转义符或特殊字符,反序列化也会失败
针对使用问题,Jackjson可以通过一些配置来帮我们解决掉;
解决办法
忽略没有映射的字段
我们反序列化的时候,在类中往往会有json
中不存在的字段,比如:
有个Properties
类,里面只定义了两个字段:
@Data
public class Properties {
private Document document;
private String name;
}
但是我们的json
字符串里面却有Properties
类没有定义的字段:
{
"properties":{
"defaultflow":false,
"executionlisteners":"",
"documentation":"",
"overrideid":"",
"name":"测试"
}
}
这个时候我们就需要忽略映射的字段
方法一:
@JsonIgnoreProperties(ignoreUnknown = true)
在POJO类中,
缺点,POJO里面组合的POJO类也得加
方法二:
建议使用
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
单引号的解决办法
//允许出现单引号
objectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true) ;
null和空串的解决办法
//支持null和空串
objectMapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
这里需要说明下,以下场景,即使进行了上面的设置,依然不能解决问题
{
//User是个POJO,并不是个字符串
//默认情况下,反序列化会失败
"User": ""
}
解决转义符和特殊字段的方法
//允许出现特殊字符和转义符
objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true) ;
解决用Jackson反序列化嵌套的字符串对象(带引号)
假设有如下
{
"id": "abcd1234",
"name": "test",
"connections": {
"default": "http://foo.com/api/",
"dev": "http://dev.foo.com/api/v2"
},
"document": "",//document本质是个类,里面有两个字段
"settings": {
"foo": "{\n \"fooId\": 1, \"token\": \"abc\"}",
"bar": "{\"barId\": 2, \"accountId\": \"d7cj3\"}"
}
}
@Data
public class Documentation {
private String name;
private String value;
}
这种情况下,我们需要对反序列化的相关类的的构建过程进行处理
@Data
public class Documentation {
private String name;
private String value;
/**
* 反序列化时需要用的,支持Documentation:""的场景。
* @param str
* @return
* @throws IOException
*/
@JsonCreator
public static Documentation create(String str) throws IOException {
//这个地方是对""进行处理
if (StringUtils.isBlank(str)) {
return null;
}
//这里其实就是将value单独抽出来,再次进行反序列化;
return (new ObjectMapper()).readValue(str, Documentation.class);
}
}
说明:
- 在反序列化时,
Jackson
默认会调用对象的无参构造函数,如果我们不定义任何构造函数,JVM
会负责生成默认的无参构造函数。但是如果我们定义了构造函数,并且没有提供无参构造函数时,Jackson会报错; - @JsonCreator 该注解用在对象的反序列时指定特定的构造函数或者工厂方法。如果默认构造函数无法满足需求,或者说我们需要在构造对象时做一些特殊逻辑,可以使用该注解。
- 如果是构造函数,则需要配合
@JsonProperty
来使用:
public class Person {
private int age;
private String name;
@JsonCreator
public Person(@JsonProperty("age") int age, @JsonProperty("name") String name) {
this.age = age;
this.name = name;
}
}
- 如果是静态方法,则不需要;例子就是我上面用的
Documentation
例子。
支持结尾逗号的场景
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=2021年09月30日-=-=-=-=-=-=-=-=-start=-=-=-=-=-=
今天遇到了json字符串中末尾存在逗号的情况:
{"solutionGroup":"insuranceDealSolution",
"processType": "TRANSFER",}
这种情况也会反序列化失败;
解决办法:
//支持结尾逗号
objectMapper.configure(JsonParser.Feature.ALLOW_TRAILING_COMMA, true);
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=2021年09月30日-=-=-=-=-=-=-=-=-end=-=-=-=-=-=
总结
Jackjson在这些特殊处理方面,确实要比fastjson要好用的多。
假设默认的反序列化的构造方法(无参数)不能满足我们的要求,那么我们就可以通过@JsonCreator
指定构造函数
或者静态方法
,其中构造方法需要配合@JsonProperty
来使用,而静态方法则不需要。
附件配置详细说明
//这个特性,决定了解析器是否将自动关闭那些不属于parser自己的输入源。
// 如果禁止,则调用应用不得不分别去关闭那些被用来创建parser的基础输入流InputStream和reader;
//默认是true
objectMapper.configure(JsonParser.Feature.AUTO_CLOSE_SOURCE, true);
//是否允许解析使用Java/C++ 样式的注释(包括'/'+'*' 和'//' 变量)
objectMapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
//设置为true时,属性名称不带双引号
objectMapper.configure(JsonGenerator.Feature.QUOTE_FIELD_NAMES, false);
//反序列化是是否允许属性名称不带双引号
objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
//是否允许单引号来包住属性名称和字符串值
objectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
//是否允许JSON字符串包含非引号控制字符(值小于32的ASCII字符,包含制表符和换行符)
objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);
//是否允许JSON整数以多个0开始
objectMapper.configure(JsonParser.Feature.ALLOW_NUMERIC_LEADING_ZEROS, true);
//null的属性不序列化
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
//按字母顺序排序属性,默认false
objectMapper.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY,true);
//是否以类名作为根元素,可以通过@JsonRootName来自定义根元素名称,默认false
objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE,true);
//是否缩放排列输出,默认false
objectMapper.configure(SerializationFeature.INDENT_OUTPUT,false);
//序列化Date日期时以timestamps输出,默认true
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS,true);
//序列化枚举是否以toString()来输出,默认false,即默认以name()来输出
objectMapper.configure(SerializationFeature.WRITE_ENUMS_USING_TO_STRING, true);
//序列化枚举是否以ordinal()来输出,默认false
objectMapper.configure(SerializationFeature.WRITE_ENUMS_USING_INDEX,false);
//序列化单元素数组时不以数组来输出,默认false
objectMapper.configure(SerializationFeature.WRITE_ENUMS_USING_TO_STRING,true);
//序列化Map时对key进行排序操作,默认false
objectMapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS,true);
//序列化char[]时以json数组输出,默认false
objectMapper.configure(SerializationFeature.WRITE_CHAR_ARRAYS_AS_JSON_ARRAYS,true);
//序列化BigDecimal时是输出原始数字还是科学计数,默认false,即以toPlainString()科学计数方式来输出
objectMapper.configure(SerializationFeature.WRITE_BIGDECIMAL_AS_PLAIN,true);
@JsonFormat(pattern = "yyyy-MM-dd'T' HH:mm:ss:SSS'Z'",timezone = "GMT+8")
时间格式注解 类型必须是Date,否则不生效
直接参考地址
Deserializing stringified (quote enclosed) nested objects with Jackson
jackson json转实体 允许特殊字符和转义字符 单引号
Add JsonParser.ALLOW_TRAILING_COMMA to work for Arrays and Objects