J'écris un sérialiseur pour sérialiser POJO en JSON mais coincé dans un problème de référence circulaire. Dans la relation bidirectionnelle un-à-plusieurs hibernate, parent référence enfant et référence enfant à parent et ici mon sérialiseur meurt. (voir exemple de code ci-dessous)
Comment briser ce cycle? Pouvons-nous obtenir l'arborescence propriétaire d'un objet pour voir si l'objet existe lui-même quelque part dans sa propre hiérarchie de propriétaires? Y a-t-il un autre moyen de savoir si la référence sera circulaire? ou toute autre idée pour résoudre ce problème?
Une relation bidirectionnelle peut-elle même être représentée dans 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 graphiques d’objets consiste à garder trace des objets que vous avez vus jusqu’à présent (à l’aide de comparaisons d’identité), pour vous empêcher de parcourir un cycle infini.
Je compte sur Google JSON Pour traiter ce type de problème à l’aide de La fonctionnalité
Exclusion des champs de la sérialisation et de la désérialisation
Supposons une relation bidirectionnelle entre les classes A et B comme suit
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 (méthode Notice 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();
Maintenant 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 oeil à GsonBuilder class
Jackson 1.6 (publié en septembre 2010) prend en charge les annotations pour gérer ce lien parent/enfant, voir http://wiki.fasterxml.com/JacksonFeatureBiDirReferences . ( Snapshot Wayback )
Bien sûr, vous pouvez déjà déjà exclure la sérialisation du lien parent en utilisant déjà la plupart des packages de traitement JSON (jackson, gson et flex-json au moins le supporter), mais le vrai truc est de le désérialiser (reconstituer le lien parent) juste gérer le côté de la sérialisation. Bien que cela ressemble pour le moment à une exclusion, cela pourrait fonctionner pour vous.
EDIT (avril 2012): Jackson 2.0 prend désormais en charge les références vraies d'identité ( Wayback Snapshot ), afin que vous puissiez le résoudre de cette façon également.
En abordant ce problème, j’ai adopté l’approche suivante (normalisation du processus dans l’application, clarification et réutilisation du code):
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);
Si vous utilisez Jackon pour la sérialisation, appliquez simplement @JsonBackReference à votre mappage bidirectionnel Il résoudra le problème de la référence circulaire.
Remarque: @JsonBackReference est utilisé pour résoudre la récursion infinie (StackOverflowError)
Utilisé une solution similaire à celle d'Arthur mais au lieu de setExclusionStrategies
j'ai utilisé
Gson gson = new GsonBuilder()
.excludeFieldsWithoutExposeAnnotation()
.create();
et utilisé @Expose
annotation gson pour les champs dont j’ai besoin dans json, les autres champs sont exclus.
Si vous utilisez le langage Javascript, il existe une solution très simple à utiliser avec le paramètre replacer
de la méthode JSON.stringify()
, dans laquelle vous pouvez transmettre une fonction pour modifier le comportement de sérialisation par défaut.
Voici comment vous pouvez l'utiliser. Prenons 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);
}
Plus tard, 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 la propriété next
pour qu'elle pointe sur l'objet réel s'il utilise une référence nommée avec un @
comme dans cet exemple.
Voici comment je l'ai finalement résolu dans mon cas. Cela fonctionne au moins avec 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;
}
Jackson fournit une annotation JsonIdentityInfo
pour empêcher les références circulaires. Vous pouvez consulter le tutoriel ici .
Cette erreur peut s'afficher lorsque vous avez deux objets:
class object1{
private object2 o2;
}
class object2{
private object1 o1;
}
En utilisant GSon pour la sérialisation, j'ai cette erreur:
Java.lang.IllegalStateException: circular reference error
Offending field: o1
Pour résoudre cela, ajoutez simplement la clé transitoire Word:
class object1{
private object2 o2;
}
class object2{
transient private object1 o1;
}
Comme vous pouvez le voir ici: Pourquoi Java a-t-il des champs transitoires?
Le mot clé transitoire en Java est utilisé pour indiquer qu'un champ ne doit pas être sérialisé.