Helpex - Trao đổi & giúp đỡ Đăng nhập
84

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?

84 hữu ích 2 bình luận 93k xem chia sẻ
14 trả lời 14
12

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 hóa dữ liệu.

Một phương pháp để xử lý 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.

12 hữu ích 4 bình luận chia sẻ
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 học GsonBuilder

46 hữu ích 2 bình luận chia sẻ
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, hã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ý bên 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 nó theo cách này.

34 hữu ích 2 bình luận chia sẻ
12

Để giải quyết vấn đề này, tôi đã thực hiện theo 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);
12 hữu ích 1 bình luận chia sẻ
4

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 chỉ thị 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 Vô hạn (StackOverflowError)

4 hữu ích 1 bình luận chia sẻ
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ừ.

2 hữu ích 1 bình luận chia sẻ
2

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;
2 hữu ích 0 bình luận chia sẻ
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 hữu ích 0 bình luận chia sẻ
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 hữu ích 0 bình luận chia sẻ
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 hữu ích 0 bình luận chia sẻ
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 .

0 hữu ích 0 bình luận chia sẻ
0

Nếu bạn sử dụng GSON để chuyển đổi lớp Java trong JSON, bạn có thể tránh các trường gây ra tham chiếu vòng tròn và vòng lặp vô hạn, bạn chỉ phải đặt chú thích @Expose trong các trường mà bạn muốn xuất hiện trong JSON và các trường không có chú thích @Expose không xuất hiện trong JSON.

Tham chiếu vòng tròn xuất hiện ví dụ nếu chúng ta cố gắng tuần tự hóa Người dùng lớp với các tuyến trường của Tuyến lớp và Tuyến lớp có người dùng trường của Người dùng lớp, thì GSON cố gắng tuần tự hóa Người dùng lớp và khi cố gắng tuần tự hóa các tuyến, tuần tự hóa lớp Route và trong lớp Route cố gắng tuần tự hóa người dùng trường, và một lần nữa cố gắng tuần tự hóa Người dùng lớp, có một tham chiếu vòng tròn kích hoạt vòng lặp vô hạn. Tôi hiển thị Người dùng và Tuyến đường lớp đã đề cập.

import com.google.gson.annotations.Expose;

Người dùng lớp

@Entity
@Table(name = "user")
public class User {
    
@Column(name = "name", nullable = false)
@Expose
private String name;

@OneToMany(mappedBy = "user", fetch = FetchType.EAGER)
@OnDelete(action = OnDeleteAction.CASCADE)
private Set<Route> routes;

@ManyToMany(fetch = FetchType.EAGER)
@OnDelete(action = OnDeleteAction.CASCADE)
@JoinTable(name = "like_", joinColumns = @JoinColumn(name = "id_user"),
        inverseJoinColumns = @JoinColumn(name = "id_route"),
        foreignKey = @ForeignKey(name = ""),
        inverseForeignKey = @ForeignKey(name = ""))
private Set<Route> likes;

Lộ trình lớp học

  @Entity
  @Table(name = "route")
  public class Route {
      
  @ManyToOne()
  @JoinColumn(nullable = false, name = "id_user", foreignKey = 
  @ForeignKey(name = "c"))    
  private User user;

Để tránh vòng lặp vô tận, chúng tôi sử dụng chú thích @Expose cung cấp GSON.

Tôi hiển thị ở định dạng JSON kết quả của việc tuần tự hóa với GSON người dùng lớp.

{
    "name": "ignacio"  
}

Chúng ta có thể thấy rằng tuyến trường và lượt thích không tồn tại ở định dạng JSON, chỉ có tên trường. Bởi vì điều này, tham chiếu vòng tròn là tránh.

Nếu chúng ta muốn sử dụng nó, chúng ta phải tạo một GSON đối tượng theo một cách cụ thể.

Gson converterJavaToJson = new GsonBuilder().setPrettyPrinting().excludeFieldsWithoutExposeAnnotation().create();

Cuối cùng, chúng tôi chuyển đổi lớp java của mô hình người dùng ngủ đông bằng cách sử dụng bộ đàm thoại mà GSON đã tạo.

 User user = createUserWithHibernate();
 String json = converterJavaToJson.toJson(user);
0 hữu ích 0 bình luận chia sẻ
-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 [Yêu cầu xử lý không thành công; ngoại lệ lồng nhau là org.springframework.http.converter.HttpMessageNotWainedException: Không thể viết JSON: Đệ quy vô hạn (StackOverflowError) (thông qua chuỗi tham chiếu: com.service.pegazo.bo.RequestMessageProfessional ["requestMessage"] -> com.service.pegazo. bo.RequestMessage ["requestMessageProfessionals"]

-3 hữu ích 1 bình luận chia sẻ
-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 đề

-11 hữu ích 0 bình luận chia sẻ
loading
Không tìm thấy câu trả lời bạn tìm kiếm? Duyệt qua các câu hỏi được gắn thẻ java hibernate json serialization , hoặc hỏi câu hỏi của bạn.

Có thể bạn quan tâm

loading