0%

关于Fastjson反序列化多态类型时@type字段位置必须位于json字符串最前面的“Feature”

本文介绍通过Fastjson反序列化多态类型时遇到的Bug。其要求@type字段位置必须位于json字符串的最前面

abstract.png

问题复现

Fastjson版本

1
2
3
4
5
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>

下述MalePeople实现了People接口后,我们对其实例进行序列化、反序列化

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
package com.aaron.JsonPathTest;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

public class Demo4 {
public static void main(String[] args) {
test1();
}

public static void test1() {
People person1 = new MalePeople("Aaron",18);
String jsonStr = JSON.toJSONString(person1, SerializerFeature.WriteClassName);

People person2 = JSON.parseObject(jsonStr, People.class);
System.out.println("json str:" + jsonStr);
System.out.println("person2 :"+person2);
}
}

interface People {
}

@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
class MalePeople implements People {
private String name;

private Integer age;
}

从序列化结果看,@type字段记录了真实类型信息。反序列化时也未出现异常

figure 1.png

但如果我们对序列化结果的json字符串进行调整,将 @type键值对 不放在json字符串的最前面。重新测试下

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void test2() {
System.out.println("Test 2 --------------------");
String jsonStr1 = "{\"@type\":\"com.aaron.JsonPathTest.MalePeople\",\"age\":18,\"name\":\"Aaron\"}";
People person1 = JSON.parseObject(jsonStr1, People.class);
System.out.println("json str 1:" + jsonStr1);
System.out.println("person 1:"+ person1);

System.out.println("--------------------------------------------");
String jsonStr2 = "{\"age\":18,\"@type\":\"com.aaron.JsonPathTest.MalePeople\",\"name\":\"Aaron\"}";
People person2 = JSON.parseObject(jsonStr2, People.class);
System.out.println("json str 2:" + jsonStr2);
System.out.println("person 2:"+ person2);
}

Bug出现了,json字符串中@type字段前面的键值对在反序列化时失败了

figure 2.png

这里补充说明下,序列化时生成的json字符串,@type字段不是在最前面的么?如代码test1方法中的jsonStr所示。为什么反序列化时拿到的json字符串中@type字段的位置顺序就变了呢?原因在于:MySQL 8.0开始提供了对JSON类型数据的支持。但JSON类型的数据是以无序的键值对形式存储的,即不保留键的顺序!!

figure 3.png

解决问题

比较简单的解决办法之一:在反序列化指定反序列化的类信息时,使用具体的实现类。而非接口

1
2
3
4
5
6
7
public static void test3() {
System.out.println("Test 3 --------------------");
String jsonStr2 = "{\"age\":18,\"@type\":\"com.aaron.JsonPathTest.MalePeople\",\"name\":\"Aaron\"}";
People person3 = JSON.parseObject(jsonStr2, MalePeople.class);
System.out.println("json str 2:" + jsonStr2);
System.out.println("person 3:"+ person3);
}

效果如下所示。此时json字符串中@type字段前面的键值对在反序列化时成功了

figure 4.png

但该方案在很多时候局限性非常大。因为其要求你在反序列化之前必须明确清楚地知道具体类型。既然问题的根源在于反序列化时@type字段在json字符串中的位置不是最前面,那我们是否可以想办法在反序列化前对json字符串进行改写?答案自然是可以的。这里我们通过JsonPath进行实现。首先引入JsonPath依赖

1
2
3
4
5
6
<!--Json Path-->
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.7.0</version>
</dependency>

利用JsonPath改写json字符串的方法,如下所示

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
package com.aaron.JsonPathTest;

import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import java.util.LinkedHashMap;
import java.util.Map;

public class JsonUtil {
private static final String TYPE_FIELD = "@type";

/**
* 将json字符串中的@type字段移动到最前面
* @param oldJsonStr
* @return
*/
public static String moveType2Head(String oldJsonStr) {
if (oldJsonStr == null) {
return null;
}

Map<String, Object> oldMap = JsonPath.read(oldJsonStr, "$");
if (!oldMap.containsKey(TYPE_FIELD)) {
return oldJsonStr;
}

// Note: 使用LinkedHashMap, 保证遍历的顺序为添加顺序
Map<String, Object> newMap = new LinkedHashMap<>();
String typeValue = (String) oldMap.remove(TYPE_FIELD);
newMap.put(TYPE_FIELD, typeValue);
newMap.putAll(oldMap);
DocumentContext newDocument = JsonPath.parse(newMap);
return newDocument.jsonString();
}
}

现在,我们来进行测试

1
2
3
4
5
6
7
8
9
public static void test4() {
System.out.println("Test 4 --------------------");
String jsonStr2 = "{\"age\":18,\"@type\":\"com.aaron.JsonPathTest.MalePeople\",\"name\":\"Aaron\"}";
String jsonStr3 = JsonUtil.moveType2Head(jsonStr2);
People person = JSON.parseObject(jsonStr3, People.class);
System.out.println("json str 2:" + jsonStr2);
System.out.println("json str 3:" + jsonStr3);
System.out.println("person :"+ person);
}

至此,反序列化成功

figure 5.png

请我喝杯咖啡捏~

欢迎关注我的微信公众号:青灯抽丝