FastJson 反序列化注意事项

不问归期 提交于 2019-11-29 22:58:19

问题描述

使用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<Hello> 的对象格式,而我们获取到的结果呢? 其内部的list为一个ArrayList对象,list中的元素为 JsonObject

输入图片说明

这种问题改如何解决:

利用 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);

注意

  • 重复引用,序列化的结果往往不是我们预期的
  • 避免循环引用
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!