2014-10-17 6 views
13

Sto riscontrando un problema nel rendere alcuni dati serializzati correttamente dal mio controller API Web ASP.NET utilizzando Newtonsoft.Json.In che modo "Really" serializza oggetti di riferimento circolari con Newtonsoft.Json?

Ecco cosa I pensa sta succedendo - correggimi se ho torto. In determinate circostanze (in particolare quando non ci sono riferimenti circolari nei dati) tutto funziona come ci si aspetterebbe: un elenco di oggetti popolati viene serializzato e restituito. Se introduco dati che causano un riferimento circolare nel modello (descritto di seguito, e anche con PreserveReferencesHandling.Objects impostato), solo gli elementi dell'elenco che portano al primo oggetto con un riferimento circolare vengono serializzati in un modo che il client può "lavorare con ". Gli "elementi che precedono" possono essere uno qualsiasi degli elementi nei dati se vengono ordinati diversamente prima di inviare le cose al serializzatore, ma almeno uno sarà serializzato in un modo in cui il client può "lavorare con". Gli oggetti vuoti finiscono per essere serializzati come riferimenti Newtonsoft ({$ref:X}).

Per esempio, se ho un modello EF completo di proprietà di navigazione che assomiglia a questo:

Model

Nel mio global.asax:

var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter; 
json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects; 

Ecco la domanda fondamentale I' facendo uso di Entity Framework (il caricamento lazy è disattivato quindi non ci sono classi proxy qui):

[HttpGet] 
[Route("starting")] 
public IEnumerable<Balance> GetStartingBalances() 
{ 
    using (MyContext db = new MyContext()) 
    { 
     var data = db.Balances 
     .Include(x => x.Source) 
     .Include(x => x.Place) 
     .ToList() 
     return data; 
    } 
} 

Finora tutto bene, data è popolato.

Se non ci sono riferimenti circolari, la vita è grandiosa. Tuttavia, non appena ci sono 2 entità con lo stesso Source o Place, la serializzazione trasforma gli ultimi oggetti Balance dell'elenco più in alto che sto tornando nei riferimenti Newtonsoft invece dei loro oggetti completi perché erano già serializzato nel Balances proprietà dell'oggetto (s) Source o Place:

[{"$id":"1","BalanceID":4,"SourceID":2,"PlaceID":2 ...Omitted for clarity...},{"$ref":"4"}] 

il problema di questo è che il cliente non sa cosa fare con {$ref:4} anche se noi esseri umani capire che cosa sta succedendo. Nel mio caso, ciò significa che non posso utilizzare AngularJS a ng-repeat sull'intero elenco di Saloni con questo JSON, perché non sono tutti veri oggetti Balance con una proprietà Balance da associare. Sono sicuro che ci sono tonnellate di altri casi d'uso che avrebbero lo stesso problema.

Non riesco a spegnere il json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects perché molte altre cose si spezzerebbero (che è ben documentato in altre 100 domande qui e altrove).

C'è una soluzione migliore per questo oltre a passare attraverso le entità nel controller API Web e facendo

Balance.Source.Balances = null; 

a tutte le proprietà di navigazione per rompere i riferimenti circolari? Perché anche quello non sembra giusto.

risposta

15

Sì, usare PreserveReferencesHandling.Objects è davvero il modo migliore per serializzare un grafico oggetto con riferimenti circolari, perché produce il JSON più compatto e conserva effettivamente la struttura di riferimento del grafico dell'oggetto. Cioè, quando si deserializza il JSON di nuovo agli oggetti (usando una libreria che comprende la notazione $id e $ref), ogni riferimento a un particolare oggetto punterà alla stessa istanza di tale oggetto, anziché avere più istanze con gli stessi dati.

Nel tuo caso il problema è che il parser lato client non comprende la notazione $id e $ref prodotta da Json.Net, quindi i riferimenti non vengono risolti. Questo problema può essere risolto utilizzando un metodo javascript per ricostruire i riferimenti agli oggetti dopo aver deserializzato il JSON. Vedere here e here per esempi.

Un'altra possibilità che potrebbe funzionare, a seconda della situazione, è quello di impostare ReferenceLoopHandling a Ignore durante la serializzazione invece di impostare PreserveReferencesHandling a Objects. Questa non è una soluzione perfetta però. Vedere this question per una spiegazione dettagliata delle differenze tra l'utilizzo di ReferenceLoopHandling.Ignore e PreserveReferencesHandling.Objects.