问题描述
使用fastJson对json字符串进行反序列化时,有几个点需要注意一下:
- 反序列化内部类
- 反序列化模板类
0. Getter/Setter问题
如我们希望返回的一个json串为
"name" : "name",
"isDeleted" : true,
"isEmpty" : 1
下面是我们的定义的dto对象,通过序列化后能得到我们预期的结果么?
private String name;
private boolean isDeleted;
private int isEmpty;
public BaseDO() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isDeleted() {
return isDeleted;
}
public void setDeleted(boolean deleted) {
isDeleted = deleted;
}
public int getIsEmpty() {
return isEmpty;
}
public void setIsEmpty(int isEmpty) {
this.isEmpty = isEmpty;
}
实际上返回的结果与我们预期的还是有差别的
解决方案:
@JSONField(name = "name")
private String name;
@JSONField(name = "isDeleted")
private boolean isDeleted;
@JSONField(name = "isEmpty")
private int isEmpty;
注意项:
- 默认构造方法
- 使用lombok注解时, 需要注意 isXxx 这种,序列化字符串中的key,可能就变成 xxx
1. 内部类问题
反序列化一个类的内部类时,可能会获取意想不到的结果,实例如下:
// 测试用例
package com.mogu.hui.study.json;
import java.util.List;
/**
* 用于测试json序列化
* Created by yihui on 16/4/22.
*/
public class JsonHello {
private String name;
private Hello hello;
public JsonHello () {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Hello getHello() {
return hello;
}
public void setHello(String hello, List<String> user) {
Hello h = new Hello();
h.setHello(hello);
h.setUser(user);
this.hello = h;
}
@Override
public String toString() {
return "JsonHello{" +
"name='" + name + '\'' +
", hello=" + hello +
'}';
}
private class Hello {
String hello;
List<String> user;
public Hello(){
}
public String getHello() {
return hello;
}
public void setHello(String hello) {
this.hello = hello;
}
public List<String> getUser() {
return user;
}
public void setUser(List<String> user) {
this.user = user;
}
@Override
public String toString() {
return "Hello{" +
"hello='" + hello + '\'' +
", user=" + user +
'}';
}
}
}
测试文件容下:
package com.mogu.hui.study.json;
import com.alibaba.fastjson.JSON;
import org.junit.Test;
import org.slf4j.LoggerFactory;
import org.slf4j.Logger;
import java.util.Arrays;
/**
* Created by yihui on 16/4/22.
*/
public class JsonTest {
private static Logger logger = LoggerFactory.getLogger(JsonTest.class);
@Test
public void innerClassTest() {
try {
JsonHello jsonHello = new JsonHello();
jsonHello.setName("hello");
jsonHello.setHello("innerHello", Arrays.asList("user1", "user2"));
String str = JSON.toJSONString(jsonHello);
logger.info("Str: {}", str);
Object obj = JSON.parseObject(str, JsonHello.class);
logger.info("Obj: {}", obj);
} catch (Exception e) {
logger.info("error: {}", e);
}
}
}
输出结果:
17:20:08.863 [main] INFO com.mogu.hui.study.json.JsonTest - Str: {"hello":{"hello":"innerHello","user":["user1","user2"]},"name":"hello"}
17:21:44.425 [main] INFO com.mogu.hui.study.json.JsonTest - Obj: JsonHello{name='hello', hello=null}
从上面的输出可以看出,反序列化对象的时候,出现诡异的事情,JsonHello对象的hello元素变成了 null
那么是如何产生这个问题的呢?
其实也简单,因为内部类,json反序列化的时候,无法得到该类,"hello":{"hello":"innerHello","user":["user1","user2"]}
这个串没法愉快的转换为 Hello
对象
这种问题如何避免?
不要反序列化匿名类,内部类!!!
2. 模板类
关于模板类,反序列化的主要问题集中在无法正确的反序列化为我们预期的对象,特别是目标对象内部嵌套有容器的时候,这种问题就更明显了,测试实例如下:
package com.mogu.hui.study.json;
import java.util.List;
/**
* 用于测试json序列化
* Created by yihui on 16/4/22.
*/
public class JsonHello<T> {
private String name;
private List<T> list;
public JsonHello () {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<T> getList() {
return list;
}
public void setList(List<T> list) {
this.list = list;
}
@Override
public String toString() {
return "JsonHello{" +
"name='" + name + '\'' +
", list=" + list +
'}';
}
}
class Hello {
String hello;
List<String> user;
public Hello(){
}
public String getHello() {
return hello;
}
public void setHello(String hello) {
this.hello = hello;
}
public List<String> getUser() {
return user;
}
public void setUser(List<String> user) {
this.user = user;
}
@Override
public String toString() {
return "Hello{" +
"hello='" + hello + '\'' +
", user=" + user +
'}';
}
}
测试类
package com.mogu.hui.study.json;
import com.alibaba.fastjson.JSON;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
/**
* Created by yihui on 16/4/22.
*/
public class JsonTest {
private static Logger logger = LoggerFactory.getLogger(JsonTest.class);
@Test
public void innerClassTest() {
try {
JsonHello<Hello> jsonHello = new JsonHello<>();
jsonHello.setName("hello");
Hello hello = new Hello();
hello.setHello("hello1");
hello.setUser(Arrays.asList("user1", "user2"));
Hello hello2 = new Hello();
hello2.setHello("hello2");
hello2.setUser(Arrays.asList("world1", "world2"));
jsonHello.setList(Arrays.asList(hello, hello2));
String str = JSON.toJSONString(jsonHello);
logger.info("Str: {}", str);
Object obj = JSON.parseObject(str, JsonHello.class);
logger.info("Obj: {}", obj);
} catch (Exception e) {
logger.info("error: {}", e);
}
}
}
聚焦在反序列化的obj对象上,反序列化的结果,debug结果如附图
我们希望转换为 JsonHello
这种问题改如何解决:
利用 TypeReference
@Test
public void innerClassTest() {
try {
JsonHello<Hello> jsonHello = new JsonHello<>();
jsonHello.setName("hello");
Hello hello = new Hello();
hello.setHello("hello1");
hello.setUser(Arrays.asList("user1", "user2"));
Hello hello2 = new Hello();
hello2.setHello("hello2");
hello2.setUser(Arrays.asList("world1", "world2"));
jsonHello.setList(Arrays.asList(hello, hello2));
String str = JSON.toJSONString(jsonHello);
logger.info("Str: {}", str);
Object obj = JSON.parseObject(str, JsonHello.class);
logger.info("Obj: {}", obj);
Object obj2 = JSON.parseObject(str, new TypeReference<JsonHello<Hello>>() {
});
logger.info("obj2: {}", obj2);
} catch (Exception e) {
logger.info("error: {}", e);
}
}
我们利用FastJson 的 parseObject(str,typeReference) 来实现反序列化的时候,得到的结果如下,完美!
3. 枚举反序列化
当序列化的对象中,包含枚举时,反序列化可能得不到你预期的结果,枚举对象变成了一个String对象, 其实和上面的问题一样,需要
package com.mogujie.service.rate.base;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
/**
* Created by yihui on 16/10/31.
*/
public class JsonTest {
public enum MyDay {
YESDAY("SUNDAY"), TODAY("MONDAY"), TOMORROW("TUESDAY");
private String today;
MyDay(String today) {
this.today = today;
}
public String getToday() {
return today;
}
@Override
public String toString() {
return "MyDay{" +
"today='" + today + '\'' +
'}';
}
}
class TTT {
MyDay myDay;
public TTT() {
}
public TTT(MyDay myDay) {
this.myDay = myDay;
}
@Override
public String toString() {
return "TTT{" +
"myDay=" + myDay +
'}';
}
}
private MyDay getDay() {
return MyDay.TODAY;
}
@Test
public void testJson() {
String str = JSON.toJSONString(getDay());
System.out.println(str);
// 这样反序列化ok
MyDay myDay = JSON.parseObject(str, MyDay.class);
System.out.println(myDay);
List<MyDay> myDayList = new ArrayList<>();
myDayList.add(MyDay.TODAY);
myDayList.add(MyDay.TOMORROW);
String str2 = JSON.toJSONString(myDayList);
System.out.println(str2);
// 反序列化失败, 和模板类的问题一样
try {
List<MyDay> out = JSON.parseObject(str2, List.class);
for (MyDay myDay1 : out) {
System.out.println(myDay1);
}
}catch (Exception e) {
e.printStackTrace();
}
// 采用这种方式,反序列化ok
TypeReference<List<MyDay>> typeReference = new TypeReference<List<MyDay>>(){
};
try {
List<MyDay> out = JSON.parseObject(str2, typeReference);
for (MyDay myDay1 : out) {
System.out.println(myDay1);
}
}catch (Exception e) {
e.printStackTrace();
}
System.out.println("------------");
TTT tt = new TTT(MyDay.TODAY);
String str3 = JSON.toJSONString(tt);
System.out.println(str3);
// 直接反序列化异常
try {
TTT recover = JSON.parseObject(str3, TTT.class);
System.out.println(recover);
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出内容
"TODAY"
MyDay{today='MONDAY'}
["TODAY","TOMORROW"]
java.lang.ClassCastException: java.lang.String cannot be cast to com.mogujie.service.rate.base.JsonTest$MyDay
at com.mogujie.service.rate.base.JsonTest.testJson(JsonTest.java:79)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.junit.runner.JUnitCore.run(JUnitCore.java:160)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:234)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:74)
MyDay{today='MONDAY'}
MyDay{today='TUESDAY'}
------------
{}
com.alibaba.fastjson.JSONException: create instance error, class com.mogujie.service.rate.base.JsonTest$TTT
at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.createInstance(JavaBeanDeserializer.java:116)
at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:356)
at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.deserialze(JavaBeanDeserializer.java:135)
at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:551)
at com.alibaba.fastjson.JSON.parseObject(JSON.java:251)
at com.alibaba.fastjson.JSON.parseObject(JSON.java:227)
at com.alibaba.fastjson.JSON.parseObject(JSON.java:186)
at com.alibaba.fastjson.JSON.parseObject(JSON.java:304)
at com.mogujie.service.rate.base.JsonTest.testJson(JsonTest.java:108)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.junit.runner.JUnitCore.run(JUnitCore.java:160)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:234)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:74)
Caused by: java.lang.NullPointerException
at com.alibaba.fastjson.parser.deserializer.JavaBeanDeserializer.createInstance(JavaBeanDeserializer.java:113)
... 29 more
Disconnected from the target VM, address: '127.0.0.1:52140', transport: 'socket'
Process finished with exit code 0
4. 重复引用
fastjson序列化的对象中,若存在重复引用的情况,序列化的结果可能不是我们预期的结果
@Test
public void testJson() {
BaseDO baseDO = new BaseDO();
baseDO.setName("base");
baseDO.setDeleted(false);
baseDO.setIsEmpty(1);
List<Object> res = new ArrayList<>();
res.add("hello");
res.add(baseDO);
res.add(123);
res.add("no");
res.add(baseDO);
res.add(10);
res.add(baseDO);
String str = JSON.toJSONString(res);
logger.info("str :{}", str);
}
从运行结果可以看出,这里对重复引用,序列化后,给出的是引用标识, 需要避免上面的方法, 可以显示关闭循环引用检测参数
String str2 = JSON.toJSONString(res, SerializerFeature.DisableCircularReferenceDetect);
我们可以考虑下,为什么fastJson默认是采取的上面的方式,而不是关闭那个配置参数?
用上面的方法可以解决重复引用的问题,但是另外一种情况呢 ? 下面的代码输出是怎样的
Map<String,Object> map = new HashMap<>();
map.put("map",map);
String str = JSON.toJSONString(map);
logger.info("str: {}", str);
String str_1 = JSON.toJSONString(map, SerializerFeature.DisableCircularReferenceDetect);
logger.info("str_1: {}", str_1);
注意
- 重复引用,序列化的结果往往不是我们预期的
- 避免循环引用