Le projet est un service Web API Web Asp.Net.
J'ai une hiérarchie de types dont j'ai besoin pour pouvoir sérialiser à et de Json, donc j'ai pris le code de ce SO: Comment implémenter JsonConverter personnalisé dans JSON.NET pour désérialiser une liste d'objets de classe de base? , et a appliqué le convertisseur à la classe de base de ma hiérarchie; quelque chose comme ceci (il y a un pseudo-code ici pour cacher les irrégularités):
[JsonConverter(typeof(TheConverter))]
public class BaseType
{
// note the base of this type here is from the linked SO above
private class TheConverter : JsonCreationConverter<BaseType>
{
protected override BaseType Create(Type objectType, JObject jObject)
{
Type actualType = GetTypeFromjObject(jObject); /*method elided*/
return (BaseType)Activator.CreateInstance(actualType);
}
}
}
public class RootType
{
public BaseType BaseTypeMember { get; set; }
}
public class DerivedType : BaseType
{
}
Donc, si je désérialise une instance RootType
dont BaseTypeMember
était égal à une instance de DerivedType
, elle sera alors désérialisée dans une instance de ce type.
Pour l'enregistrement, ces objets JSON contiennent un champ '$type'
qui contient des noms de types virtuels (et non des noms de types .Net complets) afin que je puisse simultanément prendre en charge des types dans JSON tout en contrôlant exactement quels types peuvent être sérialisés et désérialisés.
Maintenant, cela fonctionne vraiment bien pour désérialiser les valeurs de la requête; mais j'ai un problème avec la sérialisation. Si vous examinez le SO lié et la discussion Json.Net qui est liée à la réponse principale, vous verrez que le code de base que j'utilise est entièrement conçu pour la désérialisation; avec des exemples de son utilisation montrant la création manuelle du sérialiseur. L'implémentation JsonConverter
apportée à la table par ce JsonCreationConverter<T>
lève simplement un NotImplementedException
.
Maintenant, à cause de la manière dont l'API Web utilise un seul formateur pour une demande, je dois implémenter une sérialisation "standard" dans la méthode WriteObject
.
À ce stade, je dois souligner qu’avant de me lancer dans cette partie de mon projet, j’avais tout sérialisé correctement sans erreur.
Alors j'ai fait ça:
public override void WriteJson(JsonWriter writer,
object value,
JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
Mais je reçois une JsonSerializationException
: Self referencing loop detected with type 'DerivedType'
, lorsque l’un des objets est sérialisé. Encore une fois - si je supprime l'attribut du convertisseur (en désactivant ma création personnalisée), alors cela fonctionne correctement ...
J'ai l'impression que cela signifie que mon code de sérialisation déclenche de nouveau le convertisseur sur le même objet, qui à son tour appelle à nouveau le sérialiseur - ad nauseam. Confirmé - voir ma réponse
Alors quel code devrait j'écris dans WriteObject
qui fera la même sérialisation 'standard' qui fonctionne?
C'était amusant ...
Quand j'ai regardé de plus près la trace de la pile pour l'exception, j'ai remarqué que la méthode JsonSerializerInternalWriter.SerializeConvertable
y figurait deux fois. En fait, c'était cette méthode qui se trouvait au sommet de la pile - appelant JsonSerializerInternalWriter.CheckForCircularReference
- qui lançait à son tour l'exception. C'était aussi, cependant, la source de l'appel à la méthode Write
de mon propre convertisseur.
Donc, il semblerait que le sérialiseur faisait:
Donc, dans ce cas, Json.Net appelle mon convertisseur qui à son tour appelle le sérialiseur Json.Net qui explose ensuite car il voit que l'objet en cours de sérialisation lui a déjà été transmis!
Ouvrir ILSpy sur DLL (oui, je sais que c'est open source - mais je veux la fonctionnalité "appelants"!) Et déplacer la pile d'appels de SerializeConvertable
à JsonSerializerInternalWriter.SerializeValue
, le code qui détecte si un convertisseur doit être utilisé peut être trouvé juste au début:
if (((jsonConverter = ((member != null) ? member.Converter : null)) != null
|| (jsonConverter = ((containerProperty != null) ? containerProperty.ItemConverter
: null)) != null
|| (jsonConverter = ((containerContract != null) ? containerContract.ItemConverter
: null)) != null
|| (jsonConverter = valueContract.Converter) != null
|| (jsonConverter =
this.Serializer.GetMatchingConverter(valueContract.UnderlyingType)) != null
|| (jsonConverter = valueContract.InternalConverter) != null)
&& jsonConverter.CanWrite)
{
this.SerializeConvertable(writer, jsonConverter, value, valueContract,
containerContract, containerProperty);
return;
}
Heureusement, cette toute dernière condition de l'instruction if
fournit la solution à mon problème: tout ce que je devais faire était d'ajouter ce qui suit au convertisseur de base copié à partir du code contenu dans le code SO lié dans la question ou un dérivé:
public override bool CanWrite
{
get
{
return false;
}
}
Et maintenant tout fonctionne bien.
Toutefois, si vous avez l’intention d’avoir une sérialisation JSON personnalisée sur un objet et que vous l’injectez avec un convertisseuret, vous avez l’intention de revenir au mécanisme de sérialisation standard sous toutes les situations; alors vous ne pouvez pas, car vous allez tromper le cadre en pensant que vous essayez de stocker une référence circulaire.
J'ai essayé de manipuler le membre ReferenceLoopHandling
, mais si je lui ai donné Ignore
, alors rien n'a été sérialisé et si je lui ai dit de les sauvegarder, sans surprise, j'ai un débordement de pile.
Il est possible qu'il s'agisse d'un bogue dans Json.Net - d'accord, c'est tellement un cas Edge qu'il risque de tomber du bord de l'univers - mais si vous vous trouvez dans cette situation, vous êtes un peu coincé. !
J'ai rencontré ce problème avec la version 4.5.7.15008 de Newtonsoft.Json. J'ai essayé toutes les solutions proposées ici avec d'autres. J'ai résolu le problème en utilisant le code ci-dessous. En gros, vous pouvez simplement utiliser un autre JsonSerializer pour effectuer la sérialisation. Le JsonSerializer créé n'a pas de convertisseurs enregistrés, donc la ré-entrée/exception sera évitée. Si d'autres paramètres ou ContractResolver sont utilisés, ils devront les définir manuellement sur le sérialisé créé: certains arguments de constructeur peuvent être ajoutés à la classe CustomConverter pour l'adapter.
public class CustomConverter : JsonConverter
{
/// <summary>
/// Use a privately create serializer so we don't re-enter into CanConvert and cause a Newtonsoft exception
/// </summary>
private readonly JsonSerializer noRegisteredConvertersSerializer = new JsonSerializer();
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
bool meetsCondition = false; /* add condition here */
if (!meetsCondition)
writer.WriteNull();
else
noRegisteredConvertersSerializer.Serialize(writer, value);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
// example: register accepted conversion types here
return typeof(IDictionary<string, object>).IsAssignableFrom(objectType);
}
}
Je viens d'avoir le même problème avec les collections parent/enfant et j'ai trouvé ce post qui a résolu mon cas. Je voulais seulement afficher la liste des éléments de la collection parente et je n'ai besoin d'aucune donnée enfant. Par conséquent, j'utilise les éléments suivants et tout a bien fonctionné:
JsonConvert.SerializeObject(ResultGroups, Formatting.None,
new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});
il fait également référence à la page du codplex Json.NET à l'adresse:
Je viens de découvrir cela moi-même et je me tirais les cheveux de frustration!
Pour résoudre le problème, les éléments suivants ont fonctionné pour moi, mais comme j'ai raté la solution CanWrite
, la solution de contournement est plus complexe.
JsonConverter
sur la copie.WriteJson
de votre convertisseur, convertissez la valeur en votre type factice, puis sérialisez ce type à la place.Par exemple, cela ressemble à ma classe d'origine:
[JsonConverter(typeof(MyResponseConverter))]
public class MyResponse
{
public ResponseBlog blog { get; set; }
public Post[] posts { get; set; }
}
La copie ressemble à ceci:
public class FakeMyResponse
{
public ResponseBlog blog { get; set; }
public Post[] posts { get; set; }
public FakeMyResponse(MyResponse response)
{
blog = response.blog;
posts = response.posts;
}
}
WriteJson est:
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
if (CanConvert(value.GetType()))
{
FakeMyResponse response = new FakeMyResponse((MyResponse)value);
serializer.Serialize(writer, response);
}
}
Modifier:
Le PO a souligné que l'utilisation d'un Expando pourrait être une autre solution possible. Cela fonctionne bien, évitant ainsi la création de la nouvelle classe, bien que la prise en charge de DLR nécessite Framework 4.0 ou une version ultérieure. L’approche consiste à créer une nouvelle variable dynamic
ExpandoObject
, puis à initialiser ses propriétés dans la méthode WriteJson
directement pour créer la copie, par exemple:
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
if (CanConvert(value.GetType()))
{
var response = (MyResponse)value;
dynamic fake = new System.Dynamic.ExpandoObject();
fake.blog = response.blog;
fake.posts = response.posts;
serializer.Serialize(writer, fake);
}
}
OMI, ceci est une limitation sérieuse de la bibliothèque. La solution est assez simple, même si je dois admettre que cela ne m'est pas venu si vite. La solution consiste à définir:
.ReferenceLoopHandling = ReferenceLoopHandling.Serialize
qui, comme indiqué partout, éliminera l'erreur d'auto-référencement et la remplacera par un débordement de pile. Dans mon cas, j'avais besoin de la fonctionnalité d'écriture. Par conséquent, définir CanWrite sur false n'était pas une option. En fin de compte, je viens de définir un drapeau pour garder l'appel CanConvert lorsque je sais qu'un appel au sérialiseur est en train de provoquer une récursion (sans fin):
Public Class ReferencingObjectConverter : Inherits JsonConverter
Private _objects As New HashSet(Of String)
Private _ignoreNext As Boolean = False
Public Overrides Function CanConvert(objectType As Type) As Boolean
If Not _ignoreNext Then
Return GetType(IElement).IsAssignableFrom(objectType) AndAlso Not GetType(IdProperty).IsAssignableFrom(objectType)
Else
_ignoreNext = False
Return False
End If
End Function
Public Overrides Sub WriteJson(writer As JsonWriter, value As Object, serializer As JsonSerializer)
Try
If _objects.Contains(CType(value, IElement).Id.Value) Then 'insert a reference to existing serialized object
serializer.Serialize(writer, New Reference With {.Reference = CType(value, IElement).Id.Value})
Else 'add to my list of processed objects
_objects.Add(CType(value, IElement).Id.Value)
'the serialize will trigger a call to CanConvert (which is how we got here it the first place)
'and will bring us right back here with the same 'value' parameter (and SO eventually), so flag
'the CanConvert function to skip the next call.
_ignoreNext = True
serializer.Serialize(writer, value)
End If
Catch ex As Exception
Trace.WriteLine(ex.ToString)
End Try
End Sub
Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
Throw New NotImplementedException()
End Function
Private Class Reference
Public Property Reference As String
End Class
End Class
Cela pourrait aider quelqu'un, mais dans mon cas, j'essayais de remplacer la méthode Equals pour que mon objet soit traité comme type de valeur. Dans mes recherches, j'ai trouvé que JSON.NET n'aimait pas ça:
La mienne était une simple erreur et n'avait rien à voir avec la solution de ce sujet.
Ce sujet était la 1ère page de Google. Je poste ici au cas où d’autres auraient le même problème que moi.
dynamic table = new ExpandoObject();
..
..
table.rows = table; <<<<<<<< I assigned same dynamic object to itself.