对象转 JSON 可能引发栈溢出的异常,一般是因为对象中的循环引用引起不断递归。
常见的作法就是:
- 换一种 JSON 的序列化工具,比如 fastjson 默认支持消除对同一对象循环引用
- transient 修饰属性
- 显式排除对象的某些属性
1. java对象引用成环说明
1.1 相互引用成环:
class A{ B b; } class B{ A a; }
1.2 自引用成环:
class A{ A a; }
2. 引用成环造成的问题
在java中,对象引用成环问题,可以被jvm自动处理,但是将java对象转成json格式时,由于转换工具不能自行切断环,会导致无限解析最终会导致栈溢出错误。
3. 解决方法:
所有解决方法,基本原理都是将“环”切断。
1)gson提供了非常简洁的处理方式,将所有要转换成json的变量都用@Expose注解标记;将出现环的地方,不做任何处理。
2)创建gson构造器:
// 获取Gson构造器,可以过滤掉不带注解的字段 Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
3)转换json:
gson.toJson(testOject);
使用上面第一个相互引用成环的例子举例说明:
3.1 阻断环路:
class A{ @Expose B b; } class B{ A a;//不转换该字段,阻断循环引用 }
3.2 创建gson构造器,并转换json:
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation() .create();// 获取Gson构造器,可以过滤掉不带注解的字段 A testObj = new A(); String json = gson.toJson(testObj);//获取json数据
Gson 官方文档
Excluding Fields From Serialization and Deserialization
Gson supports numerous mechanisms for excluding top-level classes, fields and field types. Below are pluggable mechanisms that allow field and class exclusion. If none of the below mechanisms satisfy your needs then you can always use .
Java Modifier Exclusion
By default, if you mark a field as transient, it will be excluded. As well, if a field is marked as static then by default it will be excluded. If you want to include some transient fields then you can do the following:
import java.lang.reflect.Modifier;
Gson gson = new GsonBuilder() .excludeFieldsWithModifiers(Modifier.STATIC) .create();
NOTE: you can give any number of the Modifier constants to the excludeFieldsWithModifiers method. For example:
Gson gson = new GsonBuilder() .excludeFieldsWithModifiers(Modifier.STATIC, Modifier.TRANSIENT, Modifier.VOLATILE) .create();
Gson's @Expose
This feature provides a way where you can mark certain fields of your objects to be excluded for consideration for serialization and deserialization to JSON. To use this annotation, you must create Gson by using new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create(). The Gson instance created will exclude all fields in a class that are not marked with @Expose annotation.
User Defined Exclusion Strategies
If the above mechanisms for excluding fields and class type do not work for you then you can always write your own exclusion strategy and plug it into Gson. See the JavaDoc for more information.
The following example shows how to exclude fields marked with a specific @Foo annotation and excludes top-level types (or declared field type) of class String.
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD}) public @interface Foo { // Field tag only annotation } public class SampleObjectForTest { @Foo private final int annotatedField; private final String stringField; private final long longField; private final Class<?> clazzField; public SampleObjectForTest() { annotatedField = 5; stringField = "someDefaultValue"; longField = 1234; } } public class MyExclusionStrategy implements ExclusionStrategy { private final Class<?> typeToSkip; private MyExclusionStrategy(Class<?> typeToSkip) { this.typeToSkip = typeToSkip; } public boolean shouldSkipClass(Class<?> clazz) { return (clazz == typeToSkip); } public boolean shouldSkipField(FieldAttributes f) { return f.getAnnotation(Foo.class) != null; } } public static void main(String[] args) { Gson gson = new GsonBuilder() .setExclusionStrategies(new MyExclusionStrategy(String.class)) .serializeNulls() .create(); SampleObjectForTest src = new SampleObjectForTest(); String json = gson.toJson(src); System.out.println(json); }
The output is:
{"longField":1234}
References