Nel mio database, ho una tabella con un sacco di colonne e una di esse contiene una stringa JSON (non ho alcun controllo su questo). Qualcosa come questo:
Name Age ExtraData ---- --- ------------------ Bob 31 {c1: "1", c2: "2"} <-- string with JSON
L’endpoint dell’API Web deve restituire XML o JSON in base alle intestazioni Accept nella richiesta. Qualcosa come questo:
JSON:
{ "Name": "Bob", "Age": 31, "ExtraData": { "c1": 1, "c2": 2 } }
XML:
Bob 31 1 2
Per fare questo, ho creato una class in C # come questa:
public class Person { public string Name { get; set; } public int Age { get; set; } public Object ExtraData { get; set; } }
Quando analizzo i dati dal database, ExtraData
gli ExtraData
questo modo:
personInstance.ExtraData = JsonConvert.DeserializeObject(personTableRow.ExtraData);
Quando l’API Web restituisce JSON, tutto funziona come previsto.
Quando l’API Web restituisce XML, fornisce un’eccezione:
Il tipo ‘ObjectContent`1’ non è riuscito a serializzare il corpo della risposta per il tipo di contenuto ‘application / xml; charset = utf-8′ .
L’eccezione interna, è qualcosa di simile (non è in inglese):
Newtonsoft.Json.Linq.JToken ha un riferimento circolare e non è supportato. (O tipo ‘Newtonsoft.Json.Linq.JToken’ è un contrato di papà di colonia con ricorsiva che non contiene niente. Considere modificar a definição da coleção ‘Newtonsoft.Json.Linq.JToken’ para remover referências a si mesma.)
Esiste un modo per analizzare i dati JSON su un object senza riferimento circolare?
Hai incontrato una limitazione di XmlSerializer
. Quando deserializza un object JSON (che è un insieme non ordinato di coppie nome / valore circondate da parentesi graffe) in object
ac #, Json.NET crea un object di tipo JObject
e sfortunatamente XmlSerializer
non sa come serializzare un JObject
. In particolare cade in una ricorsione infinita cercando di serializzare i figli di JToken.Parent
. Pertanto, è necessario convertire l’ object ExtraData
sottostante object ExtraData
in un tipo che XmlSerializer
può gestire.
Tuttavia, non è ovvio quale tipo utilizzare, dal momento che:
Il tipo di c # più naturale con cui rappresentare un object JSON è un dizionario e XmlSerializer
non supporta i dizionari .
XmlSerializer
funziona per rilevamento di tipo statico . Tutti i sottotipi polimorfici object
che possono essere incontrati devono essere dichiarati tramite [XmlInclude(typof(T))]
. Tuttavia, se ciò è fatto, l’XML includerà il tipo effettivo come un attributo xsi:type
che non sembra voler nel tuo XML.
Quello che puoi fare è sfruttare la funzionalità [XmlAnyElement]
per creare una proprietà surrogata che converte l’ object ExtraData
da e verso un XElement
usando XmlNodeConverter di XmlNodeConverter
:
public class Person { public string Name { get; set; } public int Age { get; set; } [XmlIgnore] [JsonProperty] public object ExtraData { get; set; } [XmlAnyElement("ExtraData")] [JsonIgnore] public XElement ExtraDataXml { get { return JsonExtensions.SerializeExtraDataXElement("ExtraData", ExtraData); } set { ExtraData = JsonExtensions.DeserializeExtraDataXElement("ExtraData", value); } } } public static class JsonExtensions { public static XElement SerializeExtraDataXElement(string name, object extraData) { if (extraData == null) return null; var token = JToken.FromObject(extraData); if (token is JValue) { return new XElement(name, (string)token); } else if (token is JArray) { return new JObject(new JProperty(name, token)).ToXElement(false, name, true); } else { return token.ToXElement(false, name, true); } } public static object DeserializeExtraDataXElement(string name, XElement element) { object extraData; if (element == null) extraData = null; else { extraData = element.ToJToken(true, name, true); if (extraData is JObject) { var obj = (JObject)extraData; if (obj.Count == 1 && obj.Properties().First().Name == name) extraData = obj.Properties().First().Value; } if (extraData is JValue) { extraData = ((JValue)extraData).Value; } } return extraData; } public static XElement ToXElement(this JToken obj, bool omitRootObject, string deserializeRootElementName, bool writeArrayAttribute) { if (obj == null) return null; using (var reader = obj.CreateReader()) { var converter = new Newtonsoft.Json.Converters.XmlNodeConverter() { OmitRootObject = omitRootObject, DeserializeRootElementName = deserializeRootElementName, WriteArrayAttribute = writeArrayAttribute }; var jsonSerializer = JsonSerializer.CreateDefault(new JsonSerializerSettings { Converters = { converter } }); return jsonSerializer.Deserialize(reader); } } public static JToken ToJToken(this XElement xElement, bool omitRootObject, string deserializeRootElementName, bool writeArrayAttribute) { // Convert to Linq to XML JObject var settings = new JsonSerializerSettings { Converters = { new XmlNodeConverter { OmitRootObject = omitRootObject, DeserializeRootElementName = deserializeRootElementName, WriteArrayAttribute = writeArrayAttribute } } }; var root = JToken.FromObject(xElement, JsonSerializer.CreateDefault(settings)); return root; } }
Utilizzando la class di cui sopra, posso deserializzare il tuo JSON e serializzarlo in XML con il seguente risultato:
Bob 31 1 2
Si noti che ci sono incoerenze tra JSON e XML che causano problemi:
I valori primitivi JSON sono “leggermente” tipizzati (come stringa, numero, booleano o null) mentre il testo XML è completamente non tipizzato. Pertanto, i valori numerici (e le date) nel JSON vengono convertiti in XML come stringhe.
XML non ha concetto di un array. Pertanto, il JSON il cui contenitore radice è un array richiede un elemento radice sintetico da aggiungere durante la serializzazione. Questo aggiunge un po ‘di odore di codice durante il processo di conversione.
XML deve avere un singolo elemento radice mentre JSON valido può essere costituito da un valore primitivo, come una stringa. Ancora una volta è richiesto un elemento sintetico di radice durante la conversione.
Qui prototipo leggermente testato, dove dimostro che il codice funziona per ExtraData
che è un object JSON, una matrice di stringhe, una stringa singola e un valore null
.