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?
81
Antworten:
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.
quelle
Ich verlasse mich auf Google JSON, um diese Art von Problem mithilfe der Funktion zu lösen
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
quelle
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.
quelle
Um dieses Problem anzugehen, habe ich den folgenden Ansatz gewählt (Standardisierung des Prozesses in meiner Anwendung, Klarheit und Wiederverwendbarkeit des Codes):
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)
oder, um das Date-Objekt auszuschließen
quelle
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).
quelle
@JsonIgnore
IchJpaRepository
habe die Eigenschaft nicht zugeordnet, aber@JsonBackReference
densetExclusionStrategies
Ich habe eine ähnliche Lösung wie Arthur verwendet, aber stattdessen habe ich sie verwendetGson gson = new GsonBuilder() .excludeFieldsWithoutExposeAnnotation() .create();
und verwendete
@Expose
gson-Annotation für Felder, die ich im json benötige, andere Felder sind ausgeschlossen.quelle
Wenn Sie Javascript verwenden, gibt es eine sehr einfache Lösung dafür, indem Sie den
replacer
Parameter derJSON.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
next
Eigenschaft so ändern, dass sie auf das tatsächliche Objekt verweist, wenn@
in diesem Beispiel eine benannte Referenz mit einem ähnlichen Namen verwendet wird .quelle
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; }
quelle
Wenn Sie Spring Boot verwenden, gibt Jackson beim Erstellen einer Antwort aus zirkulären / bidirektionalen Daten einen Fehler aus
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;
quelle
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:
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?
quelle
Jackson bietet
JsonIdentityInfo
Anmerkungen, um Zirkelverweise zu verhindern. Sie können das Tutorial hier überprüfen .quelle
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 .quelle
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:productBean.serialBean.productBean = null;
gson.toJson();
Das sollte das Problem lösen
quelle