两个团队合作,A团队和B团队合作,A团队通过kafka发送了一段消息是JSON字符串,B团队接收到这段JSON字符串之后反序列化后,然后进行下一步的数据处理
B团队发送过来的JSON字符串,经过反序列化后,发现某个字段为空,但是消息体里是有该数据的。
此处为模拟场景,数据均是示例
@Data
static class MyTest {
private Long updateTime;
private Long uTime;
}
A团队发送过来的JSON消息是
{"updateTime":1666852055159,"uTime":1666852055159}
我们把这个字符串反序列化成MyTest对象之后,再序列化之后,得到了
{"updateTime":1666852055159}
因为参与过A团队的code review,印象中对方用的是Gson进行对象的JSON序列化,然后发送到MQ;而我们接收到消息之后是使用Jackson进行JSON反序列化。所以第一反应,就是两边的JSON序列化和反序列化使用组件不同导致。
于是我们针对同一个对象,分别进行Gson的JSON序列化和Jackson的序列化
public static void main(String[] args) {
long time = System.currentTimeMillis();
MyTest myTest = new MyTest();
myTest.setUTime(time);
myTest.setUpdateTime(time);
Gson gson = new Gson();
String json = gson.toJson(myTest);
System.out.println("gson:" + json);
System.out.println("jackson:" + JsonTool.writeToString(myTest));
}
输出结果如下:
gson:{"updateTime":1666854574241,"uTime":1666854574241}
jackson:{"updateTime":1666854574241,"utime":1666854574241}
可以发现,Gson将uTime序列化还是uTime,但是Jackson将uTime序列化成utime了,所以在反序列化的时候,Jackson无法将JSON字符串里的uTime的值绑定到uTime字段上。
后来通过查看源码发现,默认情况下,Jackson生成JSON的key的逻辑
1. 把所有field找到,作为key
2. 把所有set方法后面的字符串基于规则X生成key
3. 把所有get方法后面的字符串基于规则X生成key
4. 所有没有set和get方法的key都remove掉
规则X的逻辑:
第一个字母强制小写,从第二个字母开始找小写字母,如果是大写,就改为小写,如果是小写就把剩余的字母拼接到已有的部分key上。
譬如setUTime方法,去掉set只剩下UTime,U强制小写,有了u;继续找下一个字母,T,不是小写,改为小写,有了ut,继续找下一个字母i,是小写,就把剩余的 ime 一起拼接到已有的 ut 后面,于是有了utime。
而原本字段uTime,推测不到set方法和get方法,导致被remove掉了,setUTime和getUTime和新生成的key,“utime”绑定了。
规则X的逻辑见代码 BeanUtil#legacyManglePropertyName
protected static String legacyManglePropertyName(final String basename, final int offset)
{
final int end = basename.length();
if (end == offset) { // empty name, nope
return null;
}
// next check: is the first character upper case? If not, return as is
char c = basename.charAt(offset);
char d = Character.toLowerCase(c);
if (c == d) {
return basename.substring(offset);
}
// otherwise, lower case initial chars. Common case first, just one char
StringBuilder sb = new StringBuilder(end - offset);
sb.append(d);
int i = offset+1;
// 注意此处逻辑,从第二个字母开始遍历,找到第一个小写字母,将其整体拼接;如果是大写字母,就拼接其小写字母
// 譬如 setUTime,从 T 开始,由于T不是小写字母,如果拼接t,后面的i是小写字母,就把i到结尾整体拼接,得到time
for (; i < end; ++i) {
c = basename.charAt(i);
d = Character.toLowerCase(c);
if (c == d) {
sb.append(basename, i, end);
break;
}
sb.append(d);
}
return sb.toString();
}
而Gson默认的就是使用field的key。
以下三种方法都可以,具体场景具体选择即可