引入
首先说到jar包的引入,jackson分为三种jar包
- core
- annotation
- databind
由于databind依赖了core和annotation,所以只需要引入databind即可
demo
一个Pojo类
1
2
3
4
5
6
7
8
9
10
11
@Data
public class APojo {
private int stuId;
private String stuName;
private Date date;
private BPojo bPojo;
private List<Integer> intList;
private Map<Integer, String> map;
private int[] intArr;
private AEnum aEnum;
}
这里大致覆盖了大部分的java类型,额外的类型或者千层饼嵌套类型可以自己尝试
使用Objectapper变换jsonString和object
1
2
3
4
5
6
APojo li = new APojo(1, "li", new Date(), new BPojo("zxcdv", 2), list, map, new int[]{1, 2}, AEnum.AA);
ObjectMapper mapper = new ObjectMapper();
String s = mapper.writeValueAsString(li);
APojo aPojo = mapper.readValue(s, APojo.class);
一个write一个read就可以覆盖80%的使用场景了,所以这里不是重点,重点是序列化后的格式如何?
Tips (IMPORTANT):
需要注意,使用反序列化必须有无参的构造函数,所以有@AllArgsConstructor那也必须要配对一个@NoArgsConstructor,否则反序列化时直接报错
默认序列化格式
对于上面的对象序列化的结果如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"stuId":1,
"stuName":"li",
"date":1595515913865,
"intList":[
56,
5
],
"map":{
"1":"asdf",
"3":"zxcb"
},
"intArr":[
1,
2
],
"aenum":"AA",
"bpojo":{
"a1":"zxcdv",
"b2":2
}
}
需要注意的一些点:
- Date会被转换成时间戳
- List和array都是一样地用[item1, item2…]来表示
- map和object一样都是用{“key”: value, …}来表示
- enum的值是枚举的名称,而不是内部值(这里的内部值是一个int)
最需要注意的一点是字段名称的转换:
stuId -> stuId
bPojo -> bpojo
看到这个转换规则是不是很懵逼?
其实逻辑就是如果变量名最前面只有至多一个小写字母,那么最前面的所有大写字母都会变成小写,例如:
aAAdd -> aaadd
AAAdd -> aaadd
aaAdd -> aaAdd
AAaDD -> aaaDD
所以,如果不是全小写还是推荐使用@JsonProperty()
注解来指定序列化的名称
有趣的是在gradle中使用@AllargsConstructor,然后使用全参的构造函数会报错,可能缺少什么配置,后续跟进
常用的jackson annotation
@JsonProperty
这个应该是最常用的注解了,因为他涵盖了序列化名称,是否必须使用, 位置的索引,默认值,访问权限
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
String value() default "";
boolean required() default false;
int index() default -1;
String defaultValue() default "";
JsonProperty.Access access() default JsonProperty.Access.AUTO;
public static enum Access {
AUTO,
READ_ONLY,
WRITE_ONLY,
READ_WRITE;
private Access() {
}
}
序列化名称就是json中字段的名称,如果必须那么json反序列化时必须提供该字段否则报错
位置的索引则是字段在json中的位置(相对大小即可,无需一一紧靠,索引值越小排在越前面)
默认值(预备字段,现在还不能使用)
访问权限可以设置读、写、读写(反序列化,序列化,双向)
@JsonIgnoreProperties
这个可以指定需要忽略的字段,但是最常用的还是@JsonIgnoreProperties(ignoreUnknown = true)
,如果不使用这个,那么如果json中包括pojo中不存在的字段,则反序列化时会直接报错。
当然即便使用了,如果传入的字段的值属性有误,也会报错。
其余的我们按照读写的使用区别来分类讨论
读写
@JsonIgnore
直接在字段上使用,读写时都会忽略,不会写到json中,而生成obj时则是默认值。
@JsonIgnoreProperties
上面说了其最主要的使用方法,还可以在类上使用来一一指定需要忽略的字段@JsonIgnoreProperties({“firstName”,“lastName”})
@JsonIgnoreType
直接在类上使用,整个类都不会序列化和反序列化,例如我们将这个注释写在demo中的BPojo上,序列化的结果就没有bPojo
@JsonAutoDetect
这个属实没啥用,这个的使用场景是在没有getter、setter方法时,使用这个注解来自动侦测字段 例如:
1
2
3
4
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NON_PRIVATE)
public class APojo {
private int stuId;
Date date;
首先这个pojo没有setter和getter,然后有一个private和default的字段,我们这时候将filed可见性调整至非private,这样date就可以被序列化和反序列化 。。。多此一举
当然有多种类型可以自由组合:
可见性: fieldVisibility,getterVisibility,isGetterVisibility,setterVisibility,creatorVisibility
字段可见性:ANY, NON_PRIVATE, PROTECTED_AND_PUBLIC, PUBLIC_ONLY, NONE, DEFAULT;
读
@JsonSetter
在json字段和java字段名不同的时候使用,作用在setter方法上,例如json为id,java为stuId,那我们可以:
1
2
3
4
@JsonSetter("id")
public void setStuId(int stuId) {
this.stuId = stuId;
}
当然,我想不明白为什么不使用@JsonProperty
@JsonAnySetter
这个可能稍微有点用,我们之前说过,可能会传入不属于java的字段,我们可以使用@JsonIgnoreProperties来忽略他们,当然如果你需要记录一下这些额外字段时,你可以用这个注解配合一个map来接收这些kv
1
2
3
4
5
6
7
8
9
10
11
@JsonIgnore
private Map<String, Object> properties = new HashMap<>();
@JsonAnySetter
public void set(String fieldName, Object value){
this.properties.put(fieldName, value);
}
public Object get(String fieldName){
return this.properties.get(fieldName);
}
创建一个map,设置一个setter方法,但是这个setter方法并不是设置properties的引用,而是put kv,并在这个set方法上使用@JsonAnySetter
其次,这个map我们也不会传出去,所以使用@JsonIgnore去忽略他,有趣的是这难道不会在反序列化的时候无法写入吗? @JsonIgnore会让这个properties在反序列化时无法赋值,但是@JsonAnySetter指定的set方法其实是put方法,所以可以写入。
@JsonCreator
当没有setter或者无参构造函数时,你可以在构造方法上使用这个注解,反序列化时就可以使用有参的构造器来构建obj,但是需要指定每一个字段的json名,否则会报错
1
2
3
4
@JsonCreator
public PersonImmutable(
@JsonProperty("id") long id,
@JsonProperty("name") String name) {
@JsonDeserialize
使用自定义的反序列化器,需要指定class,并且该类需要实现一个接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class PersonDeserialize {
public long id = 0;
public String name = null;
@JsonDeserialize(using = OptimizedBooleanDeserializer.class)
public boolean enabled = false;
}
public class OptimizedBooleanDeserializer extends JsonDeserializer<Boolean> {
@Override
public Boolean deserialize(JsonParser jsonParser,
DeserializationContext deserializationContext) throws
IOException, JsonProcessingException {
String text = jsonParser.getText();
if("0".equals(text)) return false;
return true;
}
}
其中泛型指定的是返回值(java字段中的),而jsonParser.getText()
读取的是json的string value
写
介绍完了读相关的注解,写的部分注解即为读注解的逆操作,可以对照学习
@JsonInclude
这个指定了在何种情况下的字段才会导出
1
2
3
4
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class PersonInclude {
public long personId = 0;
public String name = null;
例如指定了non_empty并且name为null的话,name就不会导出到json中。
@JsonGetter
这个就是@JsonSetter的孪生兄弟,作用在Getter方法上,导出到json中的字段为注解指定的字段。
@JsonAnyGetter
@JsonAnySetter是使用一个map接收所有json中unknown的字段,而@JsonAnyGetter则是摊平(flat)map中的字段
1
2
3
4
5
private Map<Integer, String> map;
@JsonAnyGetter
public Map gettttt() {
return map;
}
假如map中包括 {a:1, b:2}
那么map中的元素也会和其他元素平级出现在json中,不过最好别用,万一重名咋整
还有就是如果使用了@JsonAnyGetter那么必须使用@JsonAnySetter,否则序列化和反序列化是不可逆的,除非他们再重新拼出一个map传入
@JsonPropertyOrder
这个顾名思义是指定json中的字段顺序的,但是和@JsonProperty使用index不同,这个需要写在类上,并且显式声明字段的顺序
1
2
3
4
5
@JsonPropertyOrder({"name", "personId"})
public class PersonPropertyOrder {
public long personId = 0;
public String name = null;
}
这样,那么就会出现在最前面
@JsonRawValue
直接输出原始值,有点像sql的${}
重点使用场景就是如果一个字段String表示的是一段json文本,并且需要输出到json作为一个可读的json obj而不是一串字符串时使用。
1
2
3
4
5
6
public class PersonRawValue {
public long personId = 0;
@JsonRawValue
public String address =
"{ \"street\" : \"Wall Street\", \"no\":1}";
}
这样,序列化后就会直接生成一个json子对象
1
{"personId":0,"address":{ "street" : "Wall Street", "no":1}}
@JsonValue
这个非常奇怪,调用序列化方法会生成一个自定义的字符串,类似toString方法
1
2
3
4
@JsonValue
public String toJson() {
return stuName + " de id is:" + stuId;
}
之后我们调用writeAsString方法就会返回这个toJson的值,相当于我们要在这个方法中自行完成json字符串的拼接。。。
那我用你这个jackson干嘛。。。
@JsonSerialize
之前介绍了custom反序列化器,现在则是custom序列化器
1
2
3
4
5
6
public class PersonSerializer {
public long personId = 0;
public String name = "John";
@JsonSerialize(using = OptimizedBooleanSerializer.class)
public boolean enabled = false;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class OptimizedBooleanSerializer extends JsonSerializer<Boolean> {
@Override
public void serialize(Boolean aBoolean, JsonGenerator jsonGenerator,
SerializerProvider serializerProvider)
throws IOException, JsonProcessingException {
if(aBoolean){
jsonGenerator.writeNumber(1);
} else {
jsonGenerator.writeNumber(0);
}
}
}
尾巴
自此,大部分的jackson注解已经记录完了,其实正常使用时基本上也就只能用到2、3个注解,因为需要额外的功能我们不需要添加注解,我们一般增加、减少java的字段即可。
使用复杂的注解徒增复杂度,也就只有在做适配器的时候才可能使用一些复杂的注解。