85 votes

Comment résoudre la référence circulaire dans le sérialiseur json causée par le mapping bidirectionnel d'hibernate ?

Je suis en train d'écrire un sérialiseur pour sérialiser POJO en JSON mais je suis bloqué par un problème de référence circulaire. Dans la relation bidirectionnelle one-to-many d'Hibernate, le parent fait référence à l'enfant et l'enfant fait référence au parent et c'est là que mon sérialise meurt. (voir l'exemple de code ci-dessous)
Comment rompre ce cycle ? Peut-on obtenir l'arbre des propriétaires d'un objet pour voir si l'objet lui-même existe quelque part dans sa propre hiérarchie de propriétaires ? Y a-t-il un autre moyen de savoir si la référence va être circulaire ? ou une autre idée pour résoudre ce problème ?

0 votes

Vouliez-vous coller un code pour que nous puissions vous aider à résoudre votre problème ?

2 votes

La solution d'eugene basée sur les annotations est correcte, mais il n'y a pas besoin d'annotation supplémentaire et d'implémentation d'ExclusionStrategy dans ce cas. Il suffit d'utiliser Java ' transitoire pour cela. Cela fonctionne pour la sérialisation d'objets Java standard, mais aussi pour la sérialisation d'objets Gson le respecte .

46voto

Arthur Ronald Points 19001

Je m'appuie sur Google JSON Pour traiter ce type de problème en utilisant la fonction

Exclusion des champs de la sérialisation et de la désérialisation

Supposons qu'une relation bidirectionnelle entre la classe A et la classe B soit la suivante

public class A implements Serializable {

    private B b;

}

Et B

public class B implements Serializable {

    private A a;

}

Utilisez maintenant GsonBuilder pour obtenir un objet Gson personnalisé comme suit (Remarque setExclusionStrategies méthode)

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

Notre référence circulaire

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

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

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

Jetez un coup d'œil à GsonBuilder classe

0 votes

Merci Arthur pour votre aimable suggestion, mais la question actuelle est de savoir quelle est la meilleure façon de construire la méthode générique "shouldSkipClass". Pour l'instant, j'ai travaillé sur l'idée de Matt et j'ai résolu mon problème, mais je reste sceptique, car à l'avenir, cette solution pourrait ne pas fonctionner dans certains scénarios.

9 votes

Cela "résout" les références circulaires en les supprimant. Il n'y a aucun moyen de reconstruire la structure de données originale à partir du JSON généré.

34voto

StaxMan Points 34626

Jackson La version 1.6 (sortie en septembre 2010) offre un support spécifique basé sur les annotations pour gérer ce type de lien parent/enfant, voir http://wiki.fasterxml.com/JacksonFeatureBiDirReferences . ( Wayback Snapshot )

Vous pouvez bien sûr déjà exclure la sérialisation du lien parent en utilisant la plupart des paquets de traitement JSON (jackson, gson et flex-json le supportent au moins), mais le vrai truc est de savoir comment le désérialiser en retour (recréer le lien parent), et pas seulement de gérer le côté sérialisation. Bien qu'il semble que pour l'instant, seule l'exclusion puisse fonctionner pour vous.

EDIT (avril 2012) : Jackson 2.0 prend désormais en charge les véritables les références d'identité ( Wayback Snapshot ), vous pouvez donc également résoudre le problème de cette manière.

0 votes

J'ai essayé de mettre les deux annotations sur les deux champs, mais cela n'a pas fonctionné : Classe A{ @JsonBackReference("abc") @JsonManagedReference("xyz") private B b ; } Classe B{ @JsonManagedReference("abc") @JsonBackReference("xyz") private A a ; }

0 votes

Comme indiqué ci-dessus, l'Object Id (@JsonIdentityInfo) est le moyen de faire fonctionner les références générales. Les références Managed/Back requièrent certaines orientations, elles ne fonctionneront donc pas dans votre cas.

12voto

matt b Points 73770

Une relation bidirectionnelle peut-elle être représentée en JSON ? Certains formats de données ne conviennent pas à certains types de modélisation de données.

Une méthode pour gérer les cycles lors de la traversée de graphes d'objets consiste à garder une trace des objets vus jusqu'à présent (en utilisant des comparaisons d'identité), afin d'éviter de parcourir un cycle infini.

0 votes

J'ai fait la même chose et cela fonctionne, mais je ne suis pas sûr que cela fonctionne dans tous les scénarios de cartographie. Au moins pour l'instant, je suis bien installé et je continuerai à réfléchir à des idées plus élégantes.

0 votes

Bien sûr qu'ils le peuvent - il n'existe tout simplement pas de type ou de structure de données natifs pour cela. Mais tout peut être représenté en XML, JSON ou dans la plupart des autres formats de données.

4 votes

Je suis curieux de savoir comment représenter une référence circulaire en JSON.

12voto

eugene Points 213

Pour résoudre ce problème, j'ai adopté l'approche suivante (standardiser le processus dans mon application, rendre le code clair et réutilisable) :

  1. Créez une classe d'annotation à utiliser sur les champs que vous souhaitez exclure.
  2. Définir une classe qui implémente l'interface ExclusionStrategy de Google
  3. Créer une méthode simple pour générer l'objet GSON en utilisant le GsonBuilder (similaire à l'explication d'Arthur)
  4. Annoter les champs à exclure si nécessaire
  5. Appliquer les règles de sérialisation à votre objet com.google.gson.Gson
  6. Sérialiser votre objet

Voici le 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)

Dans le premier cas, null est fourni au constructeur, vous pouvez spécifier une autre classe à exclure - les deux options sont ajoutées ci-dessous

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

6)

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

ou, pour exclure l'objet Date

String jsonRepresentation = _gsonObj.toJson(_myobject);

1 votes

Cela empêche l'objet enfant d'être traité, puis-je inclure le json de l'enfant et arrêter la cyclicité ?

1voto

abhishekcghosh Points 33

Si vous utilisez Javascript, il existe une solution très facile à mettre en œuvre en utilisant la fonction replacer paramètre de JSON.stringify() où vous pouvez passer une fonction pour modifier le comportement de sérialisation par défaut.

Voici comment vous pouvez l'utiliser. Considérons l'exemple ci-dessous avec 4 nœuds dans un graphe cyclique.

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

Par la suite, vous pouvez facilement recréer l'objet réel avec les références cycliques en analysant les données sérialisées et en modifiant l'élément next pour pointer vers l'objet réel s'il utilise une référence nommée avec une propriété @ comme dans cet exemple.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X