Json序列化时重复引用、循环引用的问题

在项目中,用JSONObject.parseArray(str, A.class)对Json字符串进行转换时,报以下错误:

com.alibaba.fastjson.JSONException: parseInt error, field : $ref

通过排查是循环引用/重复引用导致,问题复现:

  1. 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;
    }
}
  1. 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中对应的字符串特殊处理掉。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值