添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

Jackson [1] 是用来序列化和反序列化json的Java的开源框架。Spring的默认json解析器便是Jackson。与其他Java的json的框架Gson等相比,Jackson解析大的json文件速度比较快;Jackson运行时占用内存比较低,性能比较好;Jackson有灵活的API,可以很容易进行扩展和定制。

踩到的坑

前段时间在做公司的手机APP消息推送,在测试iOS的消息推送时,踩到了Jackson序列化/反序列化的大小写转换的坑。
在定义的DTO里有如下一些字段

1
2
3
4
5
6
7
8
9
10
@Data
public class IOSPushDto {
// some field
private String iOSSubtitle;
private String iOSRemindBody;
private String iOSMusic;
private Boolean iOSSilentNotification;
private Boolean iOSMutableContent;
//some other field
}

然后自测的时候,写了个demo接口,就直接按照DTO里定义的字段写了个json进行发送测试,然后在日志里发现各个字段的值都为null,json里的值没能塞进去。

1
2
3
4
5
6
7
{
"iOSSubtitle": "subtitle",
"iOSRemindBody": "body",
"iOSMusic": "default",
"iOSSilentNotification": false,
"iOSMutableContent":false
}

当时很是疑惑,接口没错,字段没错,为啥就塞不进去值。

问题发现

然后用fastjson做对比,手动的进行序列化以及反序列化,做了个测试,发现fastjson能正常序列化。 打印出Jackson的序列化结果后发现了问题,Jackson序列化结果的字段和原DTO的字段大小写并不一样。

1
2
3
4
5
6
7
{
"iossilentNotification": false,
"iosmutableContent": true,
"iossubtitle": "subtitle",
"iosremindBody": "body",
"iosmusic": "default"
}

我们项目使用的是lombok生成的setter与getter方法,lombok在生成方法的方法名是将各个DTO里各个字段的首字母转大写,然后前面拼生set/get方法。 这样生成的方法是这个样子的。

1
2
3
public String getIOSSubtitle() {
return iOSSubtitle;
}

Jackson在进行序列化/反序列化的时候,会通过setter/getter方法来进行序列化/反序列化。 已进行序列化为例,Jackson在将方法名截取掉getter方法后,从第一个字符开始,将大写的字符转小写,直到遇到第一个小写字符。 根据getIOSSubtitle()方法,Jackson会生成iossubtitle的json字段。反序列化同理。

如何解决

如果DTO只是全程在后端各个spring服务之间进行调用,且全程都是spring默认的Jackson进行序列化/反序列化的话,可以无视这个,按照同一套规则就不会有问题。
如果序列化/反序列化的时候使用了不同的框架,比如一个Jackson一个fastjson的话,可以在属性上加个 @JsonProperty(value = "") 注解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Data
public class IOSPushDto {
// some field
@JsonProperty("iOSSubtitle")
private String iOSSubtitle;
@JsonProperty("iOSRemindBody")
private String iOSRemindBody;
@JsonProperty("iOSMusic")
private String iOSMusic;
@JsonProperty("iOSSilentNotification")
private Boolean iOSSilentNotification;
@JsonProperty("iOSMutableContent")
private Boolean iOSMutableContent;
//some other field
}

这样Jackson在进行序列化的时候,会按照注解里的value进行序列化。 或者在类上添加这个注解 @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY,getterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)

1
2
3
4
5
6
7
8
9
10
11
@Data
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY,getterVisibility = JsonAutoDetect.Visibility.NONE, setterVisibility = JsonAutoDetect.Visibility.NONE)
public class IOSPushDto {
// some field
private String iOSSubtitle;
private String iOSRemindBody;
private String iOSMusic;
private Boolean iOSSilentNotification;
private Boolean iOSMutableContent;
//some other field
}

这个注解是设置序列化/反序列化时属性的可见性,设置field的可见性为所有,设置getter/setter方法可见性为None,这样的话就可以通过field来进行序列化/反序列化,json的字段就可以喝DTO的字段保持一致了。

一些测试

已知默认情况下 iOSSubtitle 会被序列化为 iossubtitle ,而如果字段本身就是 iossubtitle 的话,lombok会生成 getIossubtitle ,最终也会被序列化成 iossubtitle 。那么这两个字段出现在一起的时候会怎么样呢? 我们做个测试看一看会出现什么情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Data
public class IOSPushDto {
// some field

private String iOSSubtitle;
private String iossubtitle;

private String iOSRemindBody;
private String iosremindBody;

private String iOSMusic;
private String iosmusic;

private Boolean iOSSilentNotification;
private Boolean iossilentNotification;

private Boolean iOSMutableContent;
private Boolean iosmutableContent;

//some other field
}

写个main方法,看一下序列化的结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static void main(String[] args) throws JsonProcessingException {
var iosPushDto = new IOSPushDto();

iosPushDto.setIOSMusic("default");
iosPushDto.setIosmusic("default2");

iosPushDto.setIOSSubtitle("subtitle");
iosPushDto.setIossubtitle("subtitle2");

iosPushDto.setIOSRemindBody("body");
iosPushDto.setIosremindBody("body2");

iosPushDto.setIOSSilentNotification(false);
iosPushDto.setIossilentNotification(true);

iosPushDto.setIOSMutableContent(true);
iosPushDto.setIosmutableContent(false);

ObjectMapper mapper = new ObjectMapper();
String string = mapper.writeValueAsString(iosPushDto);
System.out.println(string);
}

emm,直接编译报错 我们注释掉main方法,再次编译,然后到target目录下看一下编译的class文件,然后通过idea反编译查看类里的属性和字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
public class IOSPushDto {
private String iOSSubtitle;
private String iossubtitle;
private String iOSRemindBody;
private String iosremindBody;
private String iOSMusic;
private String iosmusic;
private Boolean iOSSilentNotification;
private Boolean iossilentNotification;
private Boolean iOSMutableContent;
private Boolean iosmutableContent;

public IOSPushDto() {
}

public String getIOSSubtitle() {
return this.iOSSubtitle;
}

public String getIOSRemindBody() {
return this.iOSRemindBody;
}

public String getIOSMusic() {
return this.iOSMusic;
}

public Boolean getIOSSilentNotification() {
return this.iOSSilentNotification;
}

public Boolean getIOSMutableContent() {
return this.iOSMutableContent;
}

public void setIOSSubtitle(final String iOSSubtitle) {
this.iOSSubtitle = iOSSubtitle;
}

public void setIOSRemindBody(final String iOSRemindBody) {
this.iOSRemindBody = iOSRemindBody;
}

public void setIOSMusic(final String iOSMusic) {
this.iOSMusic = iOSMusic;
}

public void setIOSSilentNotification(final Boolean iOSSilentNotification) {
this.iOSSilentNotification = iOSSilentNotification;
}

public void setIOSMutableContent(final Boolean iOSMutableContent) {
this.iOSMutableContent = iOSMutableContent;
}

public boolean equals(final Object o) {
if (o == this) {
return true;
} else if (!(o instanceof IOSPushDto)) {
return false;
} else {
IOSPushDto other = (IOSPushDto)o;
if (!other.canEqual(this)) {
return false;
} else {
Object this$iOSSilentNotification = this.getIOSSilentNotification();
Object other$iOSSilentNotification = other.getIOSSilentNotification();
if (this$iOSSilentNotification == null) {
if (other$iOSSilentNotification != null) {
return false;
}
} else if (!this$iOSSilentNotification.equals(other$iOSSilentNotification)) {
return false;
}

Object this$iossilentNotification = this.getIOSSilentNotification();
Object other$iossilentNotification = other.getIOSSilentNotification();
if (this$iossilentNotification == null) {
if (other$iossilentNotification != null) {
return false;
}
} else if (!this$iossilentNotification.equals(other$iossilentNotification)) {
return false;
}

Object this$iOSMutableContent = this.getIOSMutableContent();
Object other$iOSMutableContent = other.getIOSMutableContent();
if (this$iOSMutableContent == null) {
if (other$iOSMutableContent != null) {
return false;
}
} else if (!this$iOSMutableContent.equals(other$iOSMutableContent)) {
return false;
}

label110: {
Object this$iosmutableContent = this.getIOSMutableContent();
Object other$iosmutableContent = other.getIOSMutableContent();
if (this$iosmutableContent == null) {
if (other$iosmutableContent == null) {
break label110;
}
} else if (this$iosmutableContent.equals(other$iosmutableContent)) {
break label110;
}

return false;
}

label103: {
Object this$iOSSubtitle = this.getIOSSubtitle();
Object other$iOSSubtitle = other.getIOSSubtitle();
if (this$iOSSubtitle == null) {
if (other$iOSSubtitle == null) {
break label103;
}
} else if (this$iOSSubtitle.equals(other$iOSSubtitle)) {
break label103;
}

return false;
}

Object this$iossubtitle = this.getIOSSubtitle();
Object other$iossubtitle = other.getIOSSubtitle();
if (this$iossubtitle == null) {
if (other$iossubtitle != null) {
return false;
}
} else if (!this$iossubtitle.equals(other$iossubtitle)) {
return false;
}

label89: {
Object this$iOSRemindBody = this.getIOSRemindBody();
Object other$iOSRemindBody = other.getIOSRemindBody();
if (this$iOSRemindBody == null) {
if (other$iOSRemindBody == null) {
break label89;
}
} else if (this$iOSRemindBody.equals(other$iOSRemindBody)) {
break label89;
}

return false;
}

label82: {
Object this$iosremindBody = this.getIOSRemindBody();
Object other$iosremindBody = other.getIOSRemindBody();
if (this$iosremindBody == null) {
if (other$iosremindBody == null) {
break label82;
}
} else if (this$iosremindBody.equals(other$iosremindBody)) {
break label82;
}

return false;
}

Object this$iOSMusic = this.getIOSMusic();
Object other$iOSMusic = other.getIOSMusic();
if (this$iOSMusic == null) {
if (other$iOSMusic != null) {
return false;
}
} else if (!this$iOSMusic.equals(other$iOSMusic)) {
return false;
}

Object this$iosmusic = this.getIOSMusic();
Object other$iosmusic = other.getIOSMusic();
if (this$iosmusic == null) {
if (other$iosmusic != null) {
return false;
}
} else if (!this$iosmusic.equals(other$iosmusic)) {
return false;
}

return true;
}
}
}

protected boolean canEqual(final Object other) {
return other instanceof IOSPushDto;
}

public int hashCode() {
int PRIME = true;
int result = 1;
Object $iOSSilentNotification = this.getIOSSilentNotification();
result = result * 59 + ($iOSSilentNotification == null ? 43 : $iOSSilentNotification.hashCode());
Object $iossilentNotification = this.getIOSSilentNotification();
result = result * 59 + ($iossilentNotification == null ? 43 : $iossilentNotification.hashCode());
Object $iOSMutableContent = this.getIOSMutableContent();
result = result * 59 + ($iOSMutableContent == null ? 43 : $iOSMutableContent.hashCode());
Object $iosmutableContent = this.getIOSMutableContent();
result = result * 59 + ($iosmutableContent == null ? 43 : $iosmutableContent.hashCode());
Object $iOSSubtitle = this.getIOSSubtitle();
result = result * 59 + ($iOSSubtitle == null ? 43 : $iOSSubtitle.hashCode());
Object $iossubtitle = this.getIOSSubtitle();
result = result * 59 + ($iossubtitle == null ? 43 : $iossubtitle.hashCode());
Object $iOSRemindBody = this.getIOSRemindBody();
result = result * 59 + ($iOSRemindBody == null ? 43 : $iOSRemindBody.hashCode());
Object $iosremindBody = this.getIOSRemindBody();
result = result * 59 + ($iosremindBody == null ? 43 : $iosremindBody.hashCode());
Object $iOSMusic = this.getIOSMusic();
result = result * 59 + ($iOSMusic == null ? 43 : $iOSMusic.hashCode());
Object $iosmusic = this.getIOSMusic();
result = result * 59 + ($iosmusic == null ? 43 : $iosmusic.hashCode());
return result;
}

public String toString() {
String var10000 = this.getIOSSubtitle();
return "IOSPushDto(iOSSubtitle=" + var10000 + ", iossubtitle=" + this.getIOSSubtitle() + ", iOSRemindBody=" + this.getIOSRemindBody() + ", iosremindBody=" + this.getIOSRemindBody() + ", iOSMusic=" + this.getIOSMusic() + ", iosmusic=" + this.getIOSMusic() + ", iOSSilentNotification=" + this.getIOSSilentNotification() + ", iossilentNotification=" + this.getIOSSilentNotification() + ", iOSMutableContent=" + this.getIOSMutableContent() + ", iosmutableContent=" + this.getIOSMutableContent() + ")";
}
}

通过反编译结果我们可以看到并没有生成 iossubtitle iosremindBody iosmusic iossilentNotification iosmutableContent 这几个小写属性的setter/getter方法。 这看起来是lombok在生成方法的时候过滤掉了这几个字段。我们把 iOSSubtitle iOSRemindBody 这两大写的字段注释掉,再编译看看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Data
public class IOSPushDto {
// some field
//private String iOSSubtitle;
private String iossubtitle;

// private String iOSRemindBody;
private String iosremindBody;

private String iOSMusic;
private String iosmusic;

private Boolean iOSSilentNotification;
private Boolean iossilentNotification;

private Boolean iOSMutableContent;
private Boolean iosmutableContent;
//some other field
}

编译生成的结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
public class IOSPushDto {
private String iossubtitle;
private String iosremindBody;
private String iOSMusic;
private String iosmusic;
private Boolean iOSSilentNotification;
private Boolean iossilentNotification;
private Boolean iOSMutableContent;
private Boolean iosmutableContent;

public IOSPushDto() {
}

public String getIossubtitle() {
return this.iossubtitle;
}

public String getIosremindBody() {
return this.iosremindBody;
}

public String getIOSMusic() {
return this.iOSMusic;
}

public Boolean getIOSSilentNotification() {
return this.iOSSilentNotification;
}

public Boolean getIOSMutableContent() {
return this.iOSMutableContent;
}

public void setIossubtitle(final String iossubtitle) {
this.iossubtitle = iossubtitle;
}

public void setIosremindBody(final String iosremindBody) {
this.iosremindBody = iosremindBody;
}

public void setIOSMusic(final String iOSMusic) {
this.iOSMusic = iOSMusic;
}

public void setIOSSilentNotification(final Boolean iOSSilentNotification) {
this.iOSSilentNotification = iOSSilentNotification;
}

public void setIOSMutableContent(final Boolean iOSMutableContent) {
this.iOSMutableContent = iOSMutableContent;
}

public boolean equals(final Object o) {
if (o == this) {
return true;
} else if (!(o instanceof IOSPushDto)) {
return false;
} else {
IOSPushDto other = (IOSPushDto)o;
if (!other.canEqual(this)) {
return false;
} else {
label107: {
Object this$iOSSilentNotification = this.getIOSSilentNotification();
Object other$iOSSilentNotification = other.getIOSSilentNotification();
if (this$iOSSilentNotification == null) {
if (other$iOSSilentNotification == null) {
break label107;
}
} else if (this$iOSSilentNotification.equals(other$iOSSilentNotification)) {
break label107;
}

return false;
}

Object this$iossilentNotification = this.getIOSSilentNotification();
Object other$iossilentNotification = other.getIOSSilentNotification();
if (this$iossilentNotification == null) {
if (other$iossilentNotification != null) {
return false;
}
} else if (!this$iossilentNotification.equals(other$iossilentNotification)) {
return false;
}

Object this$iOSMutableContent = this.getIOSMutableContent();
Object other$iOSMutableContent = other.getIOSMutableContent();
if (this$iOSMutableContent == null) {
if (other$iOSMutableContent != null) {
return false;
}
} else if (!this$iOSMutableContent.equals(other$iOSMutableContent)) {
return false;
}

label86: {
Object this$iosmutableContent = this.getIOSMutableContent();
Object other$iosmutableContent = other.getIOSMutableContent();
if (this$iosmutableContent == null) {
if (other$iosmutableContent == null) {
break label86;
}
} else if (this$iosmutableContent.equals(other$iosmutableContent)) {
break label86;
}

return false;
}

label79: {
Object this$iossubtitle = this.getIossubtitle();
Object other$iossubtitle = other.getIossubtitle();
if (this$iossubtitle == null) {
if (other$iossubtitle == null) {
break label79;
}
} else if (this$iossubtitle.equals(other$iossubtitle)) {
break label79;
}

return false;
}

label72: {
Object this$iosremindBody = this.getIosremindBody();
Object other$iosremindBody = other.getIosremindBody();
if (this$iosremindBody == null) {
if (other$iosremindBody == null) {
break label72;
}
} else if (this$iosremindBody.equals(other$iosremindBody)) {
break label72;
}

return false;
}

Object this$iOSMusic = this.getIOSMusic();
Object other$iOSMusic = other.getIOSMusic();
if (this$iOSMusic == null) {
if (other$iOSMusic != null) {
return false;
}
} else if (!this$iOSMusic.equals(other$iOSMusic)) {
return false;
}

Object this$iosmusic = this.getIOSMusic();
Object other$iosmusic = other.getIOSMusic();
if (this$iosmusic == null) {
if (other$iosmusic != null) {
return false;
}
} else if (!this$iosmusic.equals(other$iosmusic)) {
return false;
}

return true;
}
}
}

protected boolean canEqual(final Object other) {
return other instanceof IOSPushDto;
}

public int hashCode() {
int PRIME = true;
int result = 1;
Object $iOSSilentNotification = this.getIOSSilentNotification();
result = result * 59 + ($iOSSilentNotification == null ? 43 : $iOSSilentNotification.hashCode());
Object $iossilentNotification = this.getIOSSilentNotification();
result = result * 59 + ($iossilentNotification == null ? 43 : $iossilentNotification.hashCode());
Object $iOSMutableContent = this.getIOSMutableContent();
result = result * 59 + ($iOSMutableContent == null ? 43 : $iOSMutableContent.hashCode());
Object $iosmutableContent = this.getIOSMutableContent();
result = result * 59 + ($iosmutableContent == null ? 43 : $iosmutableContent.hashCode());
Object $iossubtitle = this.getIossubtitle();
result = result * 59 + ($iossubtitle == null ? 43 : $iossubtitle.hashCode());
Object $iosremindBody = this.getIosremindBody();
result = result * 59 + ($iosremindBody == null ? 43 : $iosremindBody.hashCode());
Object $iOSMusic = this.getIOSMusic();
result = result * 59 + ($iOSMusic == null ? 43 : $iOSMusic.hashCode());
Object $iosmusic = this.getIOSMusic();
result = result * 59 + ($iosmusic == null ? 43 : $iosmusic.hashCode());
return result;
}

public String toString() {
String var10000 = this.getIossubtitle();
return "IOSPushDto(iossubtitle=" + var10000 + ", iosremindBody=" + this.getIosremindBody() + ", iOSMusic=" + this.getIOSMusic() + ", iosmusic=" + this.getIOSMusic() + ", iOSSilentNotification=" + this.getIOSSilentNotification() + ", iossilentNotification=" + this.getIOSSilentNotification() + ", iOSMutableContent=" + this.getIOSMutableContent() + ", iosmutableContent=" + this.getIOSMutableContent() + ")";
}
}

这下能正常生成了 iossubtitle iosremindBody 的setter/getter方法。 这样看lombok可能为了避免一些json的序列化/反序列化框架出问题,在这种场景下做了特殊处理。那既然这样的话,我们就手动的写一下setter/getter方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@Data
public class IOSPushDto {
// some field
private String iOSSubtitle;
private String iossubtitle;

private String iOSRemindBody;
private String iosremindBody;

private String iOSMusic;
private Boolean iOSSilentNotification;
private Boolean iOSMutableContent;
//some other field

public String getIossubtitle() {
return this.iossubtitle;
}

public String getIosremindBody() {
return this.iosremindBody;
}

public void setIossubtitle(final String iossubtitle) {
this.iossubtitle = iossubtitle;
}

public void setIosremindBody(final String iosremindBody) {
this.iosremindBody = iosremindBody;
}

public String getIOSSubtitle() {
return iOSSubtitle;
}

public IOSPushDto setIOSSubtitle(String iOSSubtitle) {
this.iOSSubtitle = iOSSubtitle;
return this;
}

public String getIOSRemindBody() {
return iOSRemindBody;
}

public IOSPushDto setIOSRemindBody(String iOSRemindBody) {
this.iOSRemindBody = iOSRemindBody;
return this;
}
}

然后是main方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void main(String[] args) throws JsonProcessingException {
var iosPushDto = new IOSPushDto();

iosPushDto.setIOSSubtitle("subtitle");
iosPushDto.setIossubtitle("subtitle2");

iosPushDto.setIOSRemindBody("body");
iosPushDto.setIosremindBody("body2");

iosPushDto.setIOSMusic("default");
iosPushDto.setIOSSilentNotification(false);
iosPushDto.setIOSMutableContent(true);
ObjectMapper mapper = new ObjectMapper();
String string = mapper.writeValueAsString(iosPushDto);
System.out.println(string);
}

编译测试。然后Jackson就抛异常了 InvalidDefinitionException Jackson针对这种情况做了做了校验。想来也是,我都能想到的可能会出问题的场景,开发这个框架的大佬怎么会想不到、、、 反序列化就不测试了,结果估计也是差不多