Come serializzare il tipo di class ma non lo spazio dei nomi in una stringa Json utilizzando DataContractJsonSerializer

Sto provando a serializzare una gerarchia di classi in una stringa Json usando DataContractJsonSerializer , in un servizio WCF. il comportamento predefinito per serializzare una class derivata consiste nell’aggiungere la seguente coppia di valori chiave all’object:

"__type":"ClassName:#Namespace"

Il mio problema è che gli spazi dei nomi sono lunghi e gonfiano la stringa Json. Mi piacerebbe in qualche modo intervenire con la serializzazione e produrre invece questo:

"__type":"ClassName"

e sulla deserializzazione intervenire di nuovo per indicare il namespace corretto (che conosco in runtime).

C’è un modo per fare una cosa del genere?

Questa pagina descrive le circostanze in cui viene emessa la proprietà __type. In breve, in WCF, se si utilizza un tipo derivato e un KnownTypeAttribute, si otterrà una proprietà __type.

Esempio:

Assumere

 [DataContract] [KnownType(typeof(Subscriber))] public class Person { ... } [DataContract] public class Subscriber : Person { ... } 

Questo codice genera una proprietà __type:

  var o = new Subscriber("Fleming"); var serializer = new DataContractJsonSerializer(typeof(Person)); serializer.WriteObject(Console.OpenStandardOutput(), o); 

Ma questo codice non:

  var o = new Subscriber("Fleming"); var serializer = new DataContractJsonSerializer(typeof(Subscriber)); serializer.WriteObject(Console.OpenStandardOutput(), o); 

Si noti che il secondo snip utilizza un DCJS con lo stesso tipo dell’object serializzato.

Per evitare il tipo __, non utilizzare i tipi derivati ​​o, per la precisione, utilizzare un serializzatore digitato sul tipo che si sta effettivamente serializzando. Se la serializzazione viene eseguita implicitamente da un metodo WCF, il metodo deve essere digitato in modo appropriato. Nel mio esempio, significa che devi utilizzare un tipo di reso di “Sottoscrittore” e non il tipo genitore, “Persona”.

Il __type viene emesso nello stream JSON dal metodo Write (Write) WriteServerTypeAttribute sulla class (interna) System.Runtime.Serialization.Json.XmlJsonWriter. Non esiste un modo pubblico, documentato e supportato per modificarlo, per quanto posso dire.

Per evitare ciò, potrebbe essere necessario restituire una stringa dal metodo WCF, eseguire personalmente la serializzazione e post-elaborare il JSON emesso.


Se non ti interessa la cosa __type, ma vuoi semplicemente rimuovere lo spazio dei nomi qualificante dal valore, inserisci i tuoi tipi nello spazio dei nomi globale. In altre parole, mettili al di fuori di qualsiasi dichiarazione di namespace nel codice.

Esempio: quando i tipi di dati risiedono in un namespace e quando ho usato un tipo derivato, il JSON serializzato assomiglia a questo:

 { "__type":"Subscriber:#My.Custom.Namespace", "Index":604455, "Name":"Fleming", "Id":580540 } 

Quando i tipi di dati risiedono nello spazio dei nomi globale, appare come questo:

 { "__type":"Subscriber:#", "Index":708759, "Name":"Fleming", "Id":675323 } 

Aggiungere il parametro namespace al contratto dati fa il trucco. [DataContract(Namespace = "")]

La risposta di Cheeso fu eccellente. Ho scoperto una raffinatezza nel pulire il campo __type però:

Piuttosto che rimuovere la sottoclass dal suo spazio dei nomi, puoi aggiungere una proprietà come la seguente:

 [DataMember(Name = "__type")] public string SubclassType { get { return "Subscriber"; } set { } } 

Sei ancora bloccato con il brutto nome “__type” ma ho scoperto che, poiché stavo restituendo una lista di sottotipi, volevo comunque specificare il nome del tipo. Si potrebbe anche restituire un valore di “” per ridurre ulteriormente le dimensioni della risposta. Si potrebbe anche solo dichiarare la proprietà come:

 public string __type 

ma ho scoperto che per accentuare l’hack ho bloccato il nome di una proprietà appropriata e poi l’ho rinominato.

Joey

Nota: ho digitato questa risposta di seguito e in seguito mi sono reso conto che DataContractResolver non è attualmente supportato con DataContractJsonSerializer. Potrebbe essere presto con la prossima versione del framework, comunque. Questo è utile anche se stai guardando più di un semplice JSON.

**

È ansible eseguire questa operazione con DataContractResolver, che consente di associare i tipi alle informazioni xsi: type (__type) e viceversa in modo personalizzato.

Per fare questo, controlla questo post sul blog DataContractResolver , oltre a questo argomento concettuale , oltre a questo esempio

@Cheeso ha scritto:

Per evitare ciò, potrebbe essere necessario restituire una stringa dal metodo WCF, eseguire personalmente la serializzazione e post-elaborare il JSON emesso.

Ecco come ho implementato quella post-elaborazione. Ho pensato di pubblicarlo qui JIC potrebbe aiutare qualcun altro.

Prima un po ‘di testo per mostrare come faccio a generare la mia stringa JSON:

 // Instantiate & populate the object to be serialized to JSON SomeClass xyz = new SomeClass(); ... populate it ... // Now serialize it DataContractJsonSerializer ser = new DataContractJsonSerializer(xyz.GetType()); // Note xyz.GetType() ... serialize the object to json, many steps omitted here for brevity ... string json = sr.ReadToEnd(); 

(La serializzazione si basa su esempi tratti da https://msdn.microsoft.com/en-us/library/bb412179%28v=vs.110%29.aspx )

Si noti che [DataContract] su SomeClass non include la syntax (name="") che ho visto suggerita altrove. Questo rimuove solo lo spazio dei nomi dal __type al costo di dover adornare TUTTI i tuoi attrattivi DataContract, il che ingombra il tuo codice. Invece, il mio postprocessore gestisce il nome dell’assembly nel campo __type.

E ora la stringa json contiene il testo JSON, ma sfortunatamente include tutto quel “__type” junk che non vuoi ma non puoi sopprimere.

Quindi ecco il mio codice di post-elaborazione che lo rimuove:

 // This strips out that unsuppressable __type clutter generated by the KnownType attributes Attribute[] attrs = Attribute.GetCustomAttributes(xyz.GetType()); foreach (Attribute attr in attrs) { if (attr is KnownTypeAttribute) { KnownTypeAttribute a = (KnownTypeAttribute)attr; string find = "\"__type\":\"" + a.Type.ReflectedType.Name + "." + a.Type.Name + ":#" + a.Type.Namespace + "\","; json = json.Replace(find, ""); } } 

Questo fa alcune assunzioni, in particolare il fatto che il campo __type termina con una virgola, che presuppone che un altro campo lo segua, sebbene (a) i miei oggetti abbiano sempre almeno un campo e (b) ho scoperto che il campo __type è sempre il primo nell’output dell’object serializzato.

Come sempre, potresti dover aggiustare qualcosa alla tua situazione, ma trovo che funzioni bene per il mio.

Alcune volte ho deciso questo problema. Io uso DataContractJsonSerializer Avrai __type in json, se il tuo metodo per la serializzazione ha un parametro di class Base, ma gli dai la sottoclass come parametro. Più dettagli:

 [DataContract] [KnownType(typeof(B))] public abstract class A { [DataMember] public String S { get; set; } } [DataContract] public class B : A { [DataMember] public Int32 Age { get; set; } } public static String ToJson(this T value) { var serializer = new DataContractJsonSerializer(typeof(T)); using (var stream = new MemoryStream()) { serializer.WriteObject(stream, value); return Encoding.UTF8.GetString(stream.ToArray()); } } 

Hai due metodi per test:

 public static void ReadTypeDerived(A type) { Console.WriteLine(ToJson(type)); } 

e

 public static void ReadType(T type) { Console.WriteLine(ToJson(type)); } 

Nel primo test che hai

“{\” __ tipo \ “: \” B: # ConsoleApplication1 \ “\ “S \”: \ “Vv \”, \ “Age \”: 10}”

Nel secondo:

“{\” S \ “: \” Vv \ “\ “Age \”: 10}”