在项目中,用JSONObject.parseArray(str, A.class)对Json字符串进行转换时,报以下错误:
com.alibaba.fastjson.JSONException: parseInt error, field : $ref
通过排查是循环引用/重复引用导致,问题复现:
- User实体类
package com.xxx.entity;
import java.util.List;
import java.util.Map;
public class User {
private String name;
private int age;
private List<Order> orders;
private Map<String, Object> map;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public List<Order> getOrders() {
return orders;
}
public void setOrders(List<Order> orders) {
this.orders = orders;
}
public Map<String, Object> getMap() {
return map;
}
public void setMap(Map<String, Object> map) {
this.map = map;
}
}
- Order实体类
package com.xxx.entity;
import java.util.List;
public class Order {
private String id;
private String productName;
private User user;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
重复引用:某属性被多次设置值
package com.xxx;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.ValueFilter;
import java.io.IOException;
import java.util.*;
public class A {
public static void main(String[] args) throws IOException {
Order order1 = new Order();
order1.setId("1");
order1.setProductName("iPhone");
Order order2 = new Order();
order2.setId("2");
order2.setProductName("iPad");
User user = new User();
user.setName("张三");
user.setAge(23);
List<Order> orders = new ArrayList<Order>();
orders.add(order1);
orders.add(order1);
orders.add(order2);
user.setOrders(orders);
Map<String, Object> map = new HashMap<String, Object>();
map.put("orders", orders);
String userStr = JSON.toJSONString(user);
System.out.println(userStr);
}
}
可以看到上面代码22、23行,25、28行共两处重复引用,打印结果为:
{
"age": 23,
"name": "张三",
"orders": [{
"id": "1",
"productName": "iPhone"
}, {
"$ref": "$.orders[0]"
}, {
"id": "2",
"productName": "iPad"
}]
}
解决方法:
方案1:采用FastJson特性,JSON.toJSONString(user, SerializerFeature.DisableCircularReferenceDetect)
方案2:如果orders在业务中不需要被序列化,可以在实体类属性上加@JSONField(serialize=false)注解-FastJson特性或transient属性-Java特性
方案3:在序列化时传入SerializeFilter接口实现对属性值进行属性过滤或值替换
String s = JSON.toJSONString(user, new ValueFilter() {
@Override
public Object process(Object o, String s, Object o1) {
if (s.equals("orders")) {
return "";
}
return o1;
}
});
方案4:使用Gson实现方案3类似效果
Gson gson = new GsonBuilder()
.addSerializationExclusionStrategy(new ExclusionStrategy() {
@Override
public boolean shouldSkipField(FieldAttributes f) {
if ("orders".equals(f.getName())) return true; //排除count
return false;
}
@Override
public boolean shouldSkipClass(Class<?> clazz) {
// 直接排除某个类 ,return true为排除
return false;
}
})
.create();
String s = gson.toJson(user);
方案5:对数据进行转换,new 一个新对象,然后赋值
方案6:产生这类问题的原因大多是代码逻辑问题,可以尽可能避免这类问题
循环引用:对象内部存在相互引用
package com.xxx;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.ValueFilter;
import java.io.IOException;
import java.util.*;
public class A {
public static void main(String[] args) throws IOException {
Order order1 = new Order();
order1.setId("1");
order1.setProductName("iPhone");
Order order2 = new Order();
order2.setId("2");
order2.setProductName("iPad");
User user = new User();
user.setName("张三");
user.setAge(23);
order1.setUser(user);
List<Order> orders = new ArrayList<Order>();
orders.add(order1);
orders.add(order2);
user.setOrders(orders);
Map<String, Object> map = new HashMap<String, Object>();
map.put("orders", orders);
String userStr = JSON.toJSONString(user);
System.out.println(userStr);
}
}
可以看到20行,order引用了user,打印结果为:
{
"age": 23,
"name": "张三",
"orders": [{
"id": "1",
"productName": "iPhone",
"user": {
"$ref": "$"
}
}, {
"id": "2",
"productName": "iPad"
}]
}
这时采用JSON.toJSONString(user, SerializerFeature.DisableCircularReferenceDetect)来序列化会出现如下异常:
引用检测是FastJson提供的一种避免运行时异常的优良机制,如果关闭它会有很大可能导致循环引用时发生StackOverflowError异常。这也是FastJson默认开启引用检测的原因
Exception in thread "main" java.lang.StackOverflowError
at com.alibaba.fastjson.serializer.SerializeWriter.writeFieldValue(SerializeWriter.java:1746)
at com.alibaba.fastjson.serializer.ASMSerializer_1_User.writeDirectNonContext(Unknown Source)
at com.alibaba.fastjson.serializer.ASMSerializer_2_Order.writeDirectNonContext(Unknown Source)
at com.alibaba.fastjson.serializer.ASMSerializer_1_User.writeDirectNonContext(Unknown Source)
at com.alibaba.fastjson.serializer.ASMSerializer_2_Order.writeDirectNonContext(Unknown Source)
解决方法:
采用重新引用
解决方法里的方案2、3、4、5、6,也可在反序列化前将JSONString中对应的字符串特殊处理掉。