Wie löse ich Zirkelverweise in json serializer, die durch bidirektionales Mapping im Ruhezustand verursacht werden?

81

Ich schreibe einen Serializer, um POJO in JSON zu serialisieren, stecke aber im Zirkelverweisproblem fest. In der bidirektionalen Eins-zu-Viele-Beziehung im Ruhezustand verweist der Elternteil auf das Kind und das Kind auf den Elternteil, und hier stirbt mein Serializer. (siehe Beispielcode unten)
Wie kann dieser Zyklus unterbrochen werden? Können wir den Eigentümerbaum eines Objekts abrufen, um festzustellen, ob das Objekt selbst irgendwo in seiner eigenen Eigentümerhierarchie vorhanden ist? Gibt es eine andere Möglichkeit, um herauszufinden, ob die Referenz kreisförmig sein wird? oder eine andere Idee, um dieses Problem zu lösen?

WSK
quelle
Wollten Sie einen Code für uns einfügen, um Ihr Problem zu lösen?
Russell
2
Die auf Annotationen basierende Lösung von eugene ist in Ordnung, in diesem Fall sind jedoch keine zusätzlichen Annotationen und keine Implementierung von ExclusionStrategy erforderlich. Verwenden Sie dazu einfach das Java -Schlüsselwort ' transient '. Es funktioniert für die Standard-Serialisierung von Java-Objekten, aber auch Gson respektiert es .
MeTTeO

Antworten:

11

Kann eine bidirektionale Beziehung überhaupt in JSON dargestellt werden? Einige Datenformate eignen sich nicht für bestimmte Arten der Datenmodellierung.

Eine Methode für den Umgang mit Zyklen beim Durchlaufen von Objektgraphen besteht darin, zu verfolgen, welche Objekte Sie bisher gesehen haben (mithilfe von Identitätsvergleichen), um zu verhindern, dass Sie einen unendlichen Zyklus durchlaufen.

matt b
quelle
Ich habe das gleiche getan und es funktioniert, aber ich bin mir nicht sicher, ob es in allen Mapping-Szenarien funktioniert. Zumindest für den Moment bin ich gut aufgestellt und werde weiter daran denken, eine elegantere Idee zu haben
WSK
Natürlich können sie das - es gibt einfach keinen nativen Datentyp oder keine native Struktur dafür. Alles kann jedoch in XML, JSON oder den meisten anderen Datenformaten dargestellt werden.
StaxMan
4
Ich bin neugierig - wie würden Sie einen Zirkelverweis in JSON darstellen?
Matt B
1
Mehrere Möglichkeiten: Beachten Sie, dass es sich im Allgemeinen nicht nur um JSON handelt, sondern um eine Kombination aus JSON und einigen Metadaten, am häufigsten um Klassendefinitionen, die Sie zum Binden an / von JSON verwenden. Für JSON ist es nur eine Frage, ob eine Objektidentität verwendet oder eine Verknüpfung neu erstellt werden soll (Jackson lib bietet beispielsweise eine spezielle Möglichkeit, die Verknüpfung zwischen Eltern und Kindern darzustellen).
StaxMan
46

Ich verlasse mich auf Google JSON, um diese Art von Problem mithilfe der Funktion zu lösen

Felder von Serialisierung und Deserialisierung ausschließen

Angenommen, eine bidirektionale Beziehung zwischen A- und B-Klasse ist wie folgt

public class A implements Serializable {

    private B b;

}

Und B

public class B implements Serializable {

    private A a;

}

Verwenden Sie jetzt GsonBuilder, um ein benutzerdefiniertes Gson-Objekt wie folgt abzurufen (Notice setExclusionStrategies- Methode)

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();

Nun unser Zirkelverweis

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

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

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

Schauen Sie sich die GsonBuilder- Klasse an

Arthur Ronald
quelle
Vielen Dank an Arthur für Ihren freundlichen Vorschlag, aber die eigentliche Frage ist, wie man die sogenannte generische "shouldSkipClass" -Methode am besten erstellt. Im Moment habe ich an Mats Idee gearbeitet und mein Problem gelöst, aber immer noch skeptisch. In Zukunft könnte diese Lösung in bestimmten Szenarien brechen.
WSK
8
Dies "löst" Zirkelverweise, indem sie entfernt werden. Es gibt keine Möglichkeit, die ursprüngliche Datenstruktur aus dem generierten JSON wiederherzustellen.
Sotirios Delimanolis
34

Jackson 1.6 (veröffentlicht im September 2010) bietet spezielle Unterstützung für Anmerkungen zur Behandlung solcher Eltern / Kind-Verknüpfungen (siehe http://wiki.fasterxml.com/JacksonFeatureBiDirReferences) . ( Wayback-Schnappschuss )

Sie können die Serialisierung des übergeordneten Links natürlich bereits ausschließen, indem Sie die meisten JSON-Verarbeitungspakete verwenden (Jackson, Gson und Flex-Json unterstützen dies zumindest), aber der eigentliche Trick besteht darin, wie Sie ihn wieder deserialisieren (übergeordneten Link neu erstellen), nicht Behandeln Sie einfach die Serialisierungsseite. Obwohl es sich vorerst so anhört, könnte nur Ausschluss für Sie funktionieren.

BEARBEITEN (April 2012): Jackson 2.0 unterstützt jetzt echte Identitätsreferenzen ( Wayback Snapshot ), sodass Sie es auch auf diese Weise lösen können.

StaxMan
quelle
Wie man das zum Laufen bringt, wenn die Richtung nicht immer gleich ist. Ich habe versucht, beide Anmerkungen in beide Felder einzufügen, aber es hat nicht funktioniert: Klasse A {@JsonBackReference ("abc") @JsonManagedReference ("xyz") privat B b; } Klasse B {@JsonManagedReference ("abc") @JsonBackReference ("xyz") privat A a; }
Azi
Wie oben beschrieben, ist die Objekt-ID (@JsonIdentityInfo) die Möglichkeit, allgemeine Referenzen zum Laufen zu bringen. Verwaltete / Zurück-Referenzen erfordern bestimmte Anweisungen, sodass sie für Ihren Fall nicht funktionieren.
StaxMan
12

Um dieses Problem anzugehen, habe ich den folgenden Ansatz gewählt (Standardisierung des Prozesses in meiner Anwendung, Klarheit und Wiederverwendbarkeit des Codes):

  1. Erstellen Sie eine Anmerkungsklasse, die für Felder verwendet werden soll, die ausgeschlossen werden sollen
  2. Definieren Sie eine Klasse, die die ExclusionStrategy-Oberfläche von Google implementiert
  3. Erstellen Sie eine einfache Methode zum Generieren des GSON-Objekts mit dem GsonBuilder (ähnlich wie bei Arthur).
  4. Kommentieren Sie die Felder, die nach Bedarf ausgeschlossen werden sollen
  5. Wenden Sie die Serialisierungsregeln auf Ihr com.google.gson.Gson-Objekt an
  6. Serialisieren Sie Ihr Objekt

Hier ist der Code:

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)

Im ersten Fall wird dem Konstruktor null übergeben. Sie können eine andere Klasse angeben, die ausgeschlossen werden soll. Beide Optionen werden unten hinzugefügt

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

6)

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

oder, um das Date-Objekt auszuschließen

String jsonRepresentation = _gsonObj.toJson(_myobject);
Eugen
quelle
1
Dies verhindert, dass das
untergeordnete
3

Wenn Sie Jackon zum Serialisieren verwenden, wenden Sie einfach @JsonBackReference auf Ihre bidirektionale Zuordnung an. Dadurch wird das Zirkelreferenzproblem gelöst.

Hinweis: @JsonBackReference wird verwendet, um die unendliche Rekursion zu lösen (StackOverflowError).

Praveen Shendge
quelle
@JsonIgnoreIch JpaRepositoryhabe die Eigenschaft nicht zugeordnet, aber @JsonBackReferenceden
Zirkelverweis
2

setExclusionStrategiesIch habe eine ähnliche Lösung wie Arthur verwendet, aber stattdessen habe ich sie verwendet

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

und verwendete @Exposegson-Annotation für Felder, die ich im json benötige, andere Felder sind ausgeschlossen.

gndp
quelle
Danke Alter. Das ist für mich gearbeitet. Nach langer Recherche habe ich endlich einen Weg gefunden, um aus diesem Fehler herauszukommen.
kepy97
1

Wenn Sie Javascript verwenden, gibt es eine sehr einfache Lösung dafür, indem Sie den replacerParameter der JSON.stringify()Methode verwenden , mit der Sie eine Funktion übergeben können, um das Standard-Serialisierungsverhalten zu ändern.

Hier erfahren Sie, wie Sie es verwenden können. Betrachten Sie das folgende Beispiel mit 4 Knoten in einem zyklischen Diagramm.

// 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);
}

Später können Sie das tatsächliche Objekt mit den zyklischen Referenzen einfach neu erstellen, indem Sie die serialisierten Daten analysieren und die nextEigenschaft so ändern, dass sie auf das tatsächliche Objekt verweist, wenn @in diesem Beispiel eine benannte Referenz mit einem ähnlichen Namen verwendet wird .

abhishekcghosh
quelle
1

So habe ich es in meinem Fall endlich gelöst. Dies funktioniert zumindest bei 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;
} 
Pirho
quelle
1

Wenn Sie Spring Boot verwenden, gibt Jackson beim Erstellen einer Antwort aus zirkulären / bidirektionalen Daten einen Fehler aus

@JsonIgnoreProperties

Zirkularität zu ignorieren

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

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

Dieser Fehler kann auftreten, wenn Sie zwei Objekte haben:

class object1{
    private object2 o2;
}

class object2{
    private object1 o1;
}

Bei Verwendung von GSon für die Serialisierung ist der folgende Fehler aufgetreten:

java.lang.IllegalStateException: circular reference error

Offending field: o1

Um dies zu lösen, fügen Sie einfach das Schlüsselwort transient hinzu:

class object1{
    private object2 o2;
}

class object2{
    transient private object1 o1;
}

Wie Sie hier sehen können: Warum hat Java Übergangsfelder?

Das vorübergehende Schlüsselwort in Java wird verwendet, um anzugeben, dass ein Feld nicht serialisiert werden soll.

Kevin ABRIOUX
quelle
0

Jackson bietet JsonIdentityInfoAnmerkungen, um Zirkelverweise zu verhindern. Sie können das Tutorial hier überprüfen .

Ankur Mahajan
quelle
-3

Die Antwort Nummer 8 ist die bessere. Ich denke, wenn Sie wissen, welches Feld einen Fehler auslöst, setzen Sie das Feld nur auf null und lösen es.

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); // **
        }
    }

Um den Code lesbar zu machen, wird ein großer Kommentar vom Kommentar // **nach unten verschoben .

java.lang.StackOverflowError [Anforderungsverarbeitung fehlgeschlagen; verschachtelte Ausnahme ist org.springframework.http.converter.HttpMessageNotWritableException: JSON konnte nicht geschrieben werden: Unendliche Rekursion (StackOverflowError) (über die Referenzkette: com.service.pegazo.bo.RequestMessageProfessional ["requestMessage"] -> com.service.pego. bo.RequestMessage ["requestMessageProfessionals"]

MarcelCH
quelle
1
Es gibt keine "Antwortnummer 8". Geben Sie besser den Namen des Autors der Antwort an, auf die verwiesen wird. Der Text, den Sie hier gepostet haben, war nicht lesbar. Sehen Sie sich bitte an, wie die Antworten gepostet werden, und versuchen Sie, sie übersichtlich zu gestalten. Schließlich verstehe ich nicht, wie dies die ursprüngliche Frage beantwortet. Bitte fügen Sie weitere Details hinzu, um die Antwort zu erläutern.
AdrianHHH
-11

Zum Beispiel hat ProductBean serialBean. Die Abbildung wäre eine bidirektionale Beziehung. Wenn wir jetzt versuchen zu verwenden gson.toJson(), wird es mit Zirkelverweis enden. Um dieses Problem zu vermeiden, können Sie die folgenden Schritte ausführen:

  1. Rufen Sie die Ergebnisse aus der Datenquelle ab.
  2. Iterieren Sie die Liste und stellen Sie sicher, dass die serialBean nicht null ist, und dann
  3. einstellen productBean.serialBean.productBean = null;
  4. Dann versuchen Sie es zu verwenden gson.toJson();

Das sollte das Problem lösen

Don Bosco R.
quelle