java中json对象转换为jsonString的结果中出现$ref的问题分析

750 阅读1分钟

java中json对象转换为jsonString的结果中出现$ref的问题分析

背景

在设计开发一个树形数据结果的时候,树形结构如下图

img_12.png 发现通过com.alibaba.fastjson输出的json string中出现了ref.ref.xxx的字样,如下图类似

img_11.png 之前开发中并未遇到过类似的情况。
由于使用了递归处理树形数据结构的元素,所以怀疑是元素之间互相引用,导致输出的结果使用了特殊的表现方式
于是翻了资料w3c的资料,发现确实有这么回事
www.w3cschool.cn/fastjson/fa…

验证

package com.example.designpattern;

import com.alibaba.fastjson.JSONObject;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.HashMap;
import java.util.Map;

@SpringBootTest
@Slf4j
public class JsonTest {

    @Test
    public void test() {
        Map<String, Person> c = new HashMap<>();
        Person p1 = new Person(1, "bob", null);
        Person p2 = new Person(2, "tom", p1);
        Person p3 = new Person(3, "lihua", null);
        c.put("1", p1);
        c.put("2", p2);
        c.put("3", p3);
        //c.put("4", p3);
        log.error("c json string is {}", JSONObject.toJSONString(c));

    }

    @Data
    private static class Person {
        private int id;
        private String name;
        private Person bestFriend;

        private Person(int id, String name, Person bestFriend) {
            this.bestFriend = bestFriend;
            this.id = id;
            this.name = name;
        }
    }
}

同一个对象被多个地方引用,但是没有互相引用

如:p1

@Test
public void test(){
        Map<String, Person> c=new HashMap<>();
        Person p1=new Person(1,"bob",null);
        Person p2=new Person(2,"tom",p1);
        Person p3=new Person(3,"lihua",null);
        c.put("1",p1);
        c.put("2",p2);
        c.put("3",p3);
        //c.put("4", p3);
        log.error("c json string is {}",JSONObject.toJSONString(c));

        }

结果为 {"1":{"id":1,"name":"bob"},"2":{"bestFriend":{"ref":"ref":".1"},"id":2,"name":"tom"},"3":{"id":3,"name":"lihua"}}
出现了$ref
如:p3

@Test
public void test(){
        Map<String, Person> c=new HashMap<>();
        Person p1=new Person(1,"bob",null);
        Person p2=new Person(2,"tom",p1);
        Person p3=new Person(3,"lihua",null);
        c.put("1",p1);
        c.put("2",p2);
        c.put("3",p3);
        //c.put("4", p3);
        log.error("c json string is {}",JSONObject.toJSONString(c));

        }

结果为:{"1":{"id":1,"name":"bob"},"2":{"id":2,"name":"tom"},"3":{"id":3,"name":"lihua"},"4":{"ref":"ref":".3"}}
出现了$ref

    @Test
    public void test() {
        Map<String, Person> c = new HashMap<>();
        Person p1 = new Person(1, "bob", null);
        Person p2 = new Person(2, "tom", null);
        Person p3 = new Person(3, "lihua", null);
        Person p4 = new Person(4, "mary", null);
//        p2.setBestFriend(p1);
//        p1.setBestFriend(p2);
//
//        p3.setBestFriend(p4);
//        p4.setBestFriend(p3);

        c.put("1", p1);
        c.put("2", p2);
        c.put("3", p3);
        c.put("4", p3);
        log.error("c json string is {}", JSONObject.toJSONString(c));

    }

结果为:{"1":{"id":1,"name":"bob"},"2":{"id":2,"name":"tom"},"3":{"id":3,"name":"lihua"},"4":{"ref":"ref":".3"}} 同样出现了$ref

可见再fastjson中,只要同一个对象出现了多次,再转换为json string时,非首次出现的地方都使用了类似jsonpath的方式表示引用和值

两个对象互相引用

如:p1、p2

@Test
public void test() {
    Map<String, Person> c = new HashMap<>();
    Person p1 = new Person(1, "bob", null);
    Person p2 = new Person(2, "tom", null);
    Person p3 = new Person(3, "lihua", null);
    p2.setBestFriend(p1);
    p1.setBestFriend(p2);
    c.put("1", p1);
    c.put("2", p2);
    c.put("3", p3);
    //c.put("4", p3);
    log.error("c json string is {}", JSONObject.toJSONString(c));

}

测试结果为:

img_14.png 出现了ref,值得注意的是由于p1p2是互相循环引用的,p2引用p1在前,p1引用p2在后而使用了ref, 值得注意的是由于p1和p2是互相循环引用的,p2引用p1在前,p1引用p2在后而使用了ref: ".."表示

再加入一组互相引用 p3、p4

    @Test
    public void test() {
        Map<String, Person> c = new HashMap<>();
        Person p1 = new Person(1, "bob", null);
        Person p2 = new Person(2, "tom", null);
        Person p3 = new Person(3, "lihua", null);
        Person p4 = new Person(4, "mary", null);
        p2.setBestFriend(p1);
        p1.setBestFriend(p2);
        
        p3.setBestFriend(p4);
        p4.setBestFriend(p3);
        
        c.put("1", p1);
        c.put("2", p2);
        c.put("3", p3);
        c.put("4", p4);
        //c.put("4", p3);
        log.error("c json string is {}", JSONObject.toJSONString(c));

    }

结果为: img_15.png 这时候已经没办法区分p1、p3是引用的p2还是p4了

可见两个对象互相引用会导致同一个对象出现了1次以上,会导致转换成json string的时候使用$ref

如何关闭这种机制

局部关闭

img_16.png

非循环引用

    public void test() {
        Map<String, Person> c = new HashMap<>();
        Person p1 = new Person(1, "bob", null);
        Person p2 = new Person(2, "tom", null);
        Person p3 = new Person(3, "lihua", null);
        Person p4 = new Person(4, "mary", null);
//        p2.setBestFriend(p1);
//        p1.setBestFriend(p2);
//
//        p3.setBestFriend(p4);
//        p4.setBestFriend(p3);

        c.put("1", p1);
        c.put("2", p2);
        c.put("3", p3);
        c.put("4", p3);
        log.error("c json string is {}", JSONObject.toJSONString(c, SerializerFeature.DisableCircularReferenceDetect));

    }

结果为:{"1":{"id":1,"name":"bob"},"2":{"id":2,"name":"tom"},"3":{"id":3,"name":"lihua"},"4":{"id":3,"name":"lihua"}}
成功关闭

全局关闭

非循环引用

    public void test() {
        JSON.DEFAULT_GENERATE_FEATURE |= SerializerFeature.DisableCircularReferenceDetect.getMask();

        Map<String, Person> c = new HashMap<>();
        Person p1 = new Person(1, "bob", null);
        Person p2 = new Person(2, "tom", null);
        Person p3 = new Person(3, "lihua", null);
        Person p4 = new Person(4, "mary", null);
//        p2.setBestFriend(p1);
//        p1.setBestFriend(p2);
//
//        p3.setBestFriend(p4);
//        p4.setBestFriend(p3);

        c.put("1", p1);
        c.put("2", p2);
        c.put("3", p3);
        c.put("4", p3);
        log.error("c json string is {}", JSONObject.toJSONString(c));

    }

结果为:{"1":{"id":1,"name":"bob"},"2":{"id":2,"name":"tom"},"3":{"id":3,"name":"lihua"},"4":{"id":3,"name":"lihua"}} 成功关闭

循环引用

    @Test
    public void test() {
        JSON.DEFAULT_GENERATE_FEATURE |= SerializerFeature.DisableCircularReferenceDetect.getMask();

        Map<String, Person> c = new HashMap<>();
        Person p1 = new Person(1, "bob", null);
        Person p2 = new Person(2, "tom", null);
        Person p3 = new Person(3, "lihua", null);
        Person p4 = new Person(4, "mary", null);
        p2.setBestFriend(p1);
        p1.setBestFriend(p2);

        p3.setBestFriend(p4);
        p4.setBestFriend(p3);

        c.put("1", p1);
        c.put("2", p2);
        c.put("3", p3);
        c.put("4", p4);
        log.error("c json string is {}", JSONObject.toJSONString(c));

    }

结果为: img_18.png 死循环导致栈溢出