Làm thế nào để giải quyết tham chiếu vòng tròn trong bộ tuần tự json gây ra bởi ánh xạ hai chiều ngủ đông?


81

Tôi đang viết một trình tuần tự để tuần tự hóa POJO thành JSON nhưng bị mắc kẹt trong vấn đề tham chiếu vòng tròn. Trong mối quan hệ một-nhiều hai chiều ở chế độ ngủ đông, tham chiếu cha tham chiếu con và tham chiếu con trở lại cha mẹ và ở đây bộ nối tiếp của tôi chết. (xem mã ví dụ bên dưới)
Làm thế nào để phá vỡ chu kỳ này? Chúng ta có thể lấy cây chủ sở hữu của một đối tượng để xem liệu bản thân đối tượng có tồn tại ở đâu đó trong hệ thống phân cấp chủ sở hữu của chính nó không? Bất kỳ cách nào khác để tìm xem tham chiếu có phải là vòng tròn không? hoặc bất kỳ ý tưởng nào khác để giải quyết vấn đề này?


Ý của bạn là dán một số mã để chúng tôi giúp bạn giải quyết vấn đề của mình?
Russell

2
giải pháp dựa trên chú thích của eugene là ok, nhưng không cần thêm chú thích và triển khai ExclusiveStrategy trong trường hợp này. Chỉ cần sử dụng từ khóa Java ' thoáng qua ' cho điều đó. Nó hoạt động để tuần tự hóa đối tượng Java tiêu chuẩn nhưng Gson cũng tôn trọng nó .
MeTTeO

Câu trả lời:


11

Mối quan hệ hai chiều thậm chí có thể được biểu diễn trong JSON không? Một số định dạng dữ liệu không phù hợp với một số kiểu mô hình dữ liệu.

Một phương pháp để xử lý các chu kỳ khi xử lý đồ thị đối tượng đi ngang là theo dõi những đối tượng nào bạn đã nhìn thấy cho đến nay (sử dụng so sánh nhận dạng), để ngăn bản thân đi qua một chu kỳ vô hạn.


Tôi đã làm như vậy và nó hoạt động nhưng không chắc nó sẽ hoạt động trong tất cả các tình huống lập bản đồ. Ít nhất là cho bây giờ tôi cũng thiết lập và sẽ tiếp tục suy nghĩ về việc có ý tưởng thêm thanh lịch
WSK

Tất nhiên họ có thể - không có kiểu dữ liệu hoặc cấu trúc riêng cho việc này. Nhưng mọi thứ đều có thể được biểu diễn bằng XML, JSON hoặc hầu hết các định dạng dữ liệu khác.
StaxMan

4
Tôi tò mò - làm thế nào bạn sẽ biểu diễn một tham chiếu vòng tròn trong JSON?
matt b

1
Nhiều cách: hãy nhớ rằng nói chung nó không chỉ là JSON, mà là sự kết hợp của JSON và một số siêu dữ liệu, các định nghĩa lớp phổ biến nhất mà bạn sử dụng để liên kết với / từ JSON. Đối với JSON, vấn đề chỉ là sử dụng nhận dạng đối tượng thuộc một loại nào đó hay tạo lại liên kết (ví dụ: Jackson lib có một cách cụ thể để biểu thị liên kết mẹ / con).
StaxMan

46

Tôi dựa vào Google JSON Để xử lý loại vấn đề này bằng cách sử dụng Tính năng

Loại trừ các trường khỏi chuỗi tuần tự hóa và hủy số hóa

Giả sử mối quan hệ hai chiều giữa lớp A và lớp B như sau

public class A implements Serializable {

    private B b;

}

Và B

public class B implements Serializable {

    private A a;

}

Bây giờ sử dụng GsonBuilder Để có được một đối tượng Gson tùy chỉnh như sau (Thông báo phương thức setExclusionStrategies )

Gson gson = new GsonBuilder()
    .setExclusionStrategies(new ExclusionStrategy() {

        public boolean shouldSkipClass(Class<?> clazz) {
            return (clazz == B.class);
        }

        /**
          * Custom field exclusion goes here
          */
        public boolean shouldSkipField(FieldAttributes f) {
            return false;
        }

     })
    /**
      * Use serializeNulls method if you want To serialize null values 
      * By default, Gson does not serialize null values
      */
    .serializeNulls()
    .create();

Bây giờ tham chiếu vòng tròn của chúng tôi

A a = new A();
B b = new B();

a.setB(b);
b.setA(a);

String json = gson.toJson(a);
System.out.println(json);

Hãy xem lớp GsonBuilder


Cảm ơn Arthur về gợi ý tốt của bạn nhưng câu hỏi thực tế là cách tốt nhất để xây dựng cái gọi là phương pháp chung chung "shouldSkipClass" là gì. Hiện tại, tôi đã làm việc trên ý tưởng của matt và giải quyết vấn đề của mình nhưng vẫn còn hoài nghi, trong tương lai giải pháp này có thể phá vỡ các kịch bản chứng thực.
WSK

8
Điều này "giải quyết" các tham chiếu vòng tròn bằng cách loại bỏ chúng. Không có cách nào để xây dựng lại cấu trúc dữ liệu ban đầu từ JSON đã tạo.
Sotirios Delimanolis

34

Jackson 1.6 (phát hành vào tháng 9 năm 2010) có hỗ trợ dựa trên chú thích cụ thể để xử lý liên kết mẹ / con như vậy, xem http://wiki.fasterxml.com/JacksonFeatureBiDirRefutions . ( Ảnh chụp lại đường đi )

Tất nhiên, bạn có thể loại trừ việc tuần tự hóa liên kết mẹ đã sử dụng hầu hết các gói xử lý JSON (ít nhất là jackson, gson và flex-json hỗ trợ nó), nhưng mẹo thực sự là làm thế nào để giải mã hóa nó trở lại (tạo lại liên kết mẹ), không chỉ cần xử lý phía tuần tự hóa. Mặc dù bây giờ có vẻ như chỉ có loại trừ có thể phù hợp với bạn.

CHỈNH SỬA (tháng 4 năm 2012): Jackson 2.0 hiện hỗ trợ các tham chiếu danh tính thực ( Ảnh chụp cách quay lại ), vì vậy bạn cũng có thể giải quyết theo cách này.


làm thế nào để điều này hoạt động khi hướng của bạn không phải lúc nào cũng giống nhau .. tôi đã thử đặt cả hai chú thích vào cả hai trường nhưng nó không hoạt động: Class A {@JsonBackReference ("abc") @JsonManagedReference ("xyz") tư nhân B b; } Lớp B {@JsonManagedReference ("abc") @JsonBackReference ("xyz") private A a; }
azi

Như trên, Id đối tượng (@JsonIdentityInfo) là cách để làm cho các tham chiếu chung hoạt động. Các tham chiếu được quản lý / Quay lại yêu cầu một số hướng dẫn nhất định, vì vậy chúng sẽ không hoạt động đối với trường hợp của bạn.
StaxMan

12

Để giải quyết vấn đề này, tôi đã thực hiện cách tiếp cận sau (chuẩn hóa quy trình trên ứng dụng của tôi, làm cho mã rõ ràng và có thể sử dụng lại):

  1. Tạo một lớp chú thích để được sử dụng trên các trường bạn muốn loại trừ
  2. Xác định một lớp triển khai giao diện ExclusiveStrategy của Google
  3. Tạo một phương thức đơn giản để tạo đối tượng GSON bằng GsonBuilder (tương tự như giải thích của Arthur)
  4. Chú thích các trường cần loại trừ khi cần
  5. Áp dụng các quy tắc tuần tự hóa cho đối tượng com.google.gson.Gson của bạn
  6. Tuần tự hóa đối tượng của bạn

Đây là mã:

1)

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})
public @interface GsonExclude {

}

2)

import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;

public class GsonExclusionStrategy implements ExclusionStrategy{

    private final Class<?> typeToExclude;

    public GsonExclusionStrategy(Class<?> clazz){
        this.typeToExclude = clazz;
    }

    @Override
    public boolean shouldSkipClass(Class<?> clazz) {
        return ( this.typeToExclude != null && this.typeToExclude == clazz )
                    || clazz.getAnnotation(GsonExclude.class) != null;
    }

    @Override
    public boolean shouldSkipField(FieldAttributes f) {
        return f.getAnnotation(GsonExclude.class) != null;
    }

}

3)

static Gson createGsonFromBuilder( ExclusionStrategy exs ){
    GsonBuilder gsonbuilder = new GsonBuilder();
    gsonbuilder.setExclusionStrategies(exs);
    return gsonbuilder.serializeNulls().create();
}

4)

public class MyObjectToBeSerialized implements Serializable{

    private static final long serialVersionID = 123L;

    Integer serializeThis;
    String serializeThisToo;
    Date optionalSerialize;

    @GsonExclude
    @ManyToOne(fetch=FetchType.LAZY, optional=false)
    @JoinColumn(name="refobj_id", insertable=false, updatable=false, nullable=false)
    private MyObjectThatGetsCircular dontSerializeMe;

    ...GETTERS AND SETTERS...
}

5)

Trong trường hợp đầu tiên, null được cung cấp cho hàm tạo, bạn có thể chỉ định một lớp khác bị loại trừ - cả hai tùy chọn đều được thêm vào bên dưới

Gson gsonObj = createGsonFromBuilder( new GsonExclusionStrategy(null) );
Gson _gsonObj = createGsonFromBuilder( new GsonExclusionStrategy(Date.class) );

6)

MyObjectToBeSerialized _myobject = someMethodThatGetsMyObject();
String jsonRepresentation = gsonObj.toJson(_myobject);

hoặc, để loại trừ đối tượng Ngày

String jsonRepresentation = _gsonObj.toJson(_myobject);

1
điều này ngăn không cho đối tượng con được xử lý, tôi có thể bao gồm json con và sau đó dừng chu kỳ không
ir2pid

3

Nếu bạn đang sử dụng Jackon để tuần tự hóa, chỉ cần áp dụng @JsonBackReference cho ánh xạ hai hướng của bạn. Nó sẽ giải quyết vấn đề tham chiếu vòng tròn.

Lưu ý: @JsonBackReference được sử dụng để giải quyết đệ quy Vô hạn (StackOverflowError)


@JsonIgnorekhiến tôi JpaRepositorykhông thể ánh xạ thuộc tính nhưng đã @JsonBackReferencegiải quyết được tham chiếu vòng tròn và vẫn cho phép ánh xạ thích hợp cho thuộc tính có vấn đề
Bramastic

2

Đã sử dụng một giải pháp tương tự như của Arthur nhưng thay vì setExclusionStrategiestôi đã sử dụng

Gson gson = new GsonBuilder()
                .excludeFieldsWithoutExposeAnnotation()
                .create();

và sử dụng @Exposechú thích gson cho các trường mà tôi cần trong json, các trường khác bị loại trừ.


Cảm ơn anh bạn. Điều này được làm việc cho tôi. Sau một thời gian dài nghiên cứu, cuối cùng tôi đã tìm ra cách để thoát khỏi lỗi này.
kepy97

1

Nếu bạn đang sử dụng Javascript, có một giải pháp rất dễ dàng cho điều đó bằng cách sử dụng replacertham số của JSON.stringify()phương thức nơi bạn có thể truyền một hàm để sửa đổi hành vi tuần tự hóa mặc định.

Đây là cách bạn có thể sử dụng nó. Hãy xem xét ví dụ dưới đây với 4 nút trong đồ thị tuần hoàn.

// node constructor
function Node(key, value) {
    this.name = key;
    this.value = value;
    this.next = null;
}

//create some nodes
var n1 = new Node("A", 1);
var n2 = new Node("B", 2);
var n3 = new Node("C", 3);
var n4 = new Node("D", 4);

// setup some cyclic references
n1.next = n2;
n2.next = n3;
n3.next = n4;
n4.next = n1;

function normalStringify(jsonObject) {
    // this will generate an error when trying to serialize
    // an object with cyclic references
    console.log(JSON.stringify(jsonObject));
}

function cyclicStringify(jsonObject) {
    // this will successfully serialize objects with cyclic
    // references by supplying @name for an object already
    // serialized instead of passing the actual object again,
    // thus breaking the vicious circle :)
    var alreadyVisited = [];
    var serializedData = JSON.stringify(jsonObject, function(key, value) {
        if (typeof value == "object") {
            if (alreadyVisited.indexOf(value.name) >= 0) {
                // do something other that putting the reference, like 
                // putting some name that you can use to build the 
                // reference again later, for eg.
                return "@" + value.name;
            }
            alreadyVisited.push(value.name);
        }
        return value;
    });
    console.log(serializedData);
}

Sau đó, bạn có thể dễ dàng tạo lại đối tượng thực với các tham chiếu theo chu kỳ bằng cách phân tích cú pháp dữ liệu được tuần tự hóa và sửa đổi thuộc nexttính để trỏ đến đối tượng thực nếu nó sử dụng một tham chiếu được đặt tên @tương tự trong ví dụ này.


1

Đây là cách cuối cùng tôi đã giải quyết nó trong trường hợp của tôi. Điều này ít nhất là hiệu quả với Gson & Jackson.

private static final Gson gson = buildGson();

private static Gson buildGson() {
    return new GsonBuilder().addSerializationExclusionStrategy( getExclusionStrategy() ).create();  
}

private static ExclusionStrategy getExclusionStrategy() {
    ExclusionStrategy exlStrategy = new ExclusionStrategy() {
        @Override
        public boolean shouldSkipField(FieldAttributes fas) {
            return ( null != fas.getAnnotation(ManyToOne.class) );
        }
        @Override
        public boolean shouldSkipClass(Class<?> classO) {
            return ( null != classO.getAnnotation(ManyToOne.class) );
        }
    };
    return exlStrategy;
} 

1

nếu bạn đang sử dụng khởi động lò xo, Jackson sẽ gây ra lỗi trong khi tạo phản hồi từ dữ liệu tròn / hai chiều, vì vậy hãy sử dụng

@JsonIgnoreProperties

bỏ qua sự tuần hoàn

At Parent:
@OneToMany(mappedBy="dbApp")
@JsonIgnoreProperties("dbApp")
private Set<DBQuery> queries;

At child:
@ManyToOne
@JoinColumn(name = "db_app_id")
@JsonIgnoreProperties("queries")
private DBApp dbApp;

0

Lỗi này có thể được khắc phục khi bạn có hai đối tượng:

class object1{
    private object2 o2;
}

class object2{
    private object1 o1;
}

Với việc sử dụng GSon để tuần tự hóa, tôi đã gặp lỗi này:

java.lang.IllegalStateException: circular reference error

Offending field: o1

Để giải quyết vấn đề này, chỉ cần thêm từ khóa tạm thời:

class object1{
    private object2 o2;
}

class object2{
    transient private object1 o1;
}

Như bạn có thể thấy ở đây: Tại sao Java có các trường tạm thời?

Từ khóa tạm thời trong Java được sử dụng để chỉ ra rằng một trường không nên được tuần tự hóa.


0

Jackson cung cấp JsonIdentityInfochú thích để ngăn chặn các tham chiếu vòng tròn. Bạn có thể kiểm tra hướng dẫn ở đây .


-3

câu trả lời số 8 là tốt hơn, tôi nghĩ vì vậy nếu bạn biết trường nào đang gây ra lỗi, bạn chỉ đặt fild là null và giải quyết.

List<RequestMessage> requestMessages = lazyLoadPaginated(first, pageSize, sortField, sortOrder, filters, joinWith);
    for (RequestMessage requestMessage : requestMessages) {
        Hibernate.initialize(requestMessage.getService());
        Hibernate.initialize(requestMessage.getService().getGroupService());
        Hibernate.initialize(requestMessage.getRequestMessageProfessionals());
        for (RequestMessageProfessional rmp : requestMessage.getRequestMessageProfessionals()) {
            Hibernate.initialize(rmp.getProfessional());
            rmp.setRequestMessage(null); // **
        }
    }

Để làm cho mã có thể đọc được, một bình luận lớn được chuyển từ bình luận // **xuống bên dưới.

java.lang.StackOverflowError [Xử lý yêu cầu không thành công; ngoại lệ lồng nhau là org.springframework.http.converter.HttpMessageNotW ... bo.RequestMessage ["requestMessageProfessionals"]


1
Không có "câu trả lời số 8", bạn nên cung cấp tên tác giả của câu trả lời được tham khảo. Văn bản bạn đã đăng ở đây không thể đọc được, vui lòng xem cách các câu trả lời được đăng và cố gắng sắp xếp chúng gọn gàng. Cuối cùng tôi không hiểu làm thế nào điều này trả lời câu hỏi ban đầu. Vui lòng thêm chi tiết để giải thích câu trả lời.
AdrianHHH

-11

Ví dụ: ProductBean có serialBean. Ánh xạ sẽ là mối quan hệ hai chiều. Nếu bây giờ chúng ta cố gắng sử dụng gson.toJson(), nó sẽ kết thúc với tham chiếu vòng tròn. Để tránh vấn đề đó, bạn có thể làm theo các bước sau:

  1. Lấy kết quả từ nguồn dữ liệu.
  2. Lặp lại danh sách và đảm bảo rằng serialBean không rỗng, sau đó
  3. Bộ productBean.serialBean.productBean = null;
  4. Sau đó, hãy thử sử dụng gson.toJson();

Điều đó sẽ giải quyết vấn đề

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.