2012-04-26 6 views
9

Sto pianificando di creare un WCFservice che restituisca oggetti di dizionario generici serializzati su JSON. Sfortunatamente, la serializzazione fallisce, poiché l'oggetto è potenzialmente sempre diverso. KnownTypes non può aiutare, perché il tipo di proprietà è Dictionary, e non posso dire KnownType, in quanto la classe sarà potenzialmente sempre diversa.Posso serializzare il dizionario <stringa, oggetto> utilizzando il serializzatore DataContract?

Qualche idea se è possibile serializzare un "tipo sconosciuto"?

Non mi dispiace specificare DataContract/DataMember per ciascuna delle mie classi, ma (almeno per la versione del prototipo) non voglio avere tipi forti per ciascuna risposta. Il client Javascript non ha importanza.

E le lezioni anonime?

+0

Sono contento che alla gente piaccia già. Per favore non ti preoccupare di rispondere con argomenti come non è stato fatto per WCF ecc, ecc. Apprezzerei se puoi farci sapere se c'è un modo noto o se sei stato lì e rinunci per ragioni valide (cosa motivi?). – tishma

risposta

7

NOTA: Sono entrato in molti dettagli su JavaScriptSerializer all'inizio della mia risposta, se si desidera solo leggere la risoluzione del problema noto di tipo menzionato nella domanda originale, saltare alla fine della risposta.

prestazioni

Basato sul benchmarks mi sono imbattuto, il JavaScriptSerializer è molto più lenta rispetto alle altre alternative e possono prendere 2 volte più a lungo per serializzare/deserializzare un oggetto rispetto al DataContractSerializer.

Non c'è bisogno di tipo noto

Detto questo, il JavaScriptSerializer è più flessibile in quanto non richiede di specificare 'tipi noti' prima del tempo, e il JSON serializzato è più pulita almeno in il caso dei dizionari (vedere esempi here).

L'altra faccia della flessibilità rispetto ai tipi noti è che non sarà possibile deserializzare quella stessa stringa JSON al tipo originale.Per esempio, supponiamo di avere una semplice Person classe:

public class Person 
{ 
    public string Name { get; set; } 

    public int Age { get; set; } 
} 

E se creo un 'istanza di Dictinoary<string, object> e aggiungere un'istanza della classe Person ad esso prima di serializzazione:

var dictionary = new Dictionary<string, object>(); 
dictionary.Add("me", new Person { Name = "Yan", Age = 30 }); 
var serializer = new new JavaScriptSerializer(); 
var json = serializer .Serialize(dictionary); 

io ottenere il seguente JSON {"me":{"Name":"Yan","Age":30}} che è molto pulito ma privo di qualsiasi tipo di informazione. Quindi suppone se si dispone di due classi con le stesse definizioni membri o se Person è una sottoclasse senza introdurre ulteriori membri:

public class Employee : Person 
{ 
} 

poi semplicemente non c'è alcun modo per il serializzatore di essere in grado di garantire che il JSON {"Name":"Yan","Age":30} può essere deserializzato al tipo corretto.

Se si deserializzare {"me":{"Name":"Yan","Age":30}} utilizzando il JavaScriptSerializer, nel dizionario si ottiene indietro il valore associato a "me" non è un'istanza di Person ma un Dictionary<string, object> invece, un semplice elenco di proprietà.

Se si desidera ottenere un'istanza Person indietro, si potrebbe (anche se molto probabilmente non vorrebbe mai!) Conversione che Dictionary<string, object> utilizzando il metodo ConvertToType helper:

var clone = serializer.Deserialize<Dictionary<string, object>>(json); 
var personClone = serializer.ConverToType<Person>(clone["me"]); 

D'altra parte, se si non c'è bisogno di preoccuparsi di deserializzare quei JSON nel tipo corretto e la serailizzazione di JSON non è un collo di bottiglia nelle prestazioni (profila il tuo codice e scopri quanto tempo di CPU è speso per la serializzazione se non lo hai già fatto) quindi direi basta usare JavaScriptSerializer.

Iniezione di tipo noto

Se, alla fine della giornata, si ha ancora bisogno di utilizzare DataContractSerializer e la necessità di iniettare queste KnownTypes, qui ci sono due cose che si possono provare.

1) Passare la matrice di tipi noti a DataContractSerializer constructor.

2) Far passare una sottoclasse di DataContractResolver (con i mezzi per individuare i tipi di interesse a voi) per il DataContractSerializer constructor

È possibile creare un 'noto tipo di registro' di sorta che tiene traccia dei tipi che possono essere aggiunti al dizionario, e se è possibile controllare tutti i tipi che avrete bisogno di iniettare al DataContractSerializer, si può provare la cosa più semplice:

  1. Creare una classe KnownTypeRegister con metodi statici per aggiungere un tipo alla lista dei tipi noti:

    public static class KnownTypeRegister 
    { 
        private static readonly ConcurrentBag _knownTypes = new ConcurrentBag(); 
        public static void Add(Type type) 
        { 
         _knownTypes.Add(type); 
        } 
        public static IEnumerable Get() 
        { 
         return _knownTypes.ToArray(); 
        } 
    }
  2. Aggiungere un costruttore statico che registra i tipi con il registro:

    [DataContract] 
    public class Person 
    { 
        static Person() 
        { 
         KnownTypeRegister.Add(typeof(Person)); 
        } 
        [DataMember] 
        public string Name { get; set; } 
        [DataMember] 
        public int Age { get; set; } 
    }
  3. Ottenere la matrice dei tipi conosciuti dal registro quando si costruisce il serializzatore:

var serializer = new DataContractSerializer(typeof(Dictionary<string, object>), KnownTypeRegister.Get());

Altre opzioni dinamiche/migliori sono possibili e ma sono anche più difficili da implementare, se vuoi saperne di più sulla risoluzione dei tipi noti dinamici, dai un'occhiata all'articolo MSDN di Juval Lowy sull'argomento here. Inoltre, this blog post di Carlos Figueira entra anche nei dettagli su tecniche più avanzate come generare dinamicamente i tipi, merita una lettura mentre sei sull'argomento!

+0

Prima di tutto, grazie per la risposta dettagliata. Sembra davvero che valga la pena indagare lungo le linee. Ho anche imparato a conoscere ServiceStack sul tuo blog, e sicuramente suona molto più semplice per costruire un servizio JSON REST di qualsiasi altra cosa. – tishma

1

disclaimer Non è consigliabile esporre object su un endpoint WCF; anche se questo appare "flessibile", questa non è una buona idea in quanto non hai specificato quale tipo di informazioni saranno servite dal tuo servizio.

e ora una risposta

Se la chiamata WCF viene consumato da una chiamata AJAX e come dici tu Javascript client just doesn't care. allora perché non rendere la chiamata WCF semplicemente restituire una stringa? Poi i meccanismi interni della vostra chiamata WCF possono serializzare l'Dictionary<string, object> utilizzando il JavaScriptSerializer

public string MyServiceMethod() 
{ 
    var hash = new Dictionary<string, object>(); 
    hash.Add("key1", "val1"); 
    hash.Add("key2", new { Field1 = "field1", Field2 = 42}); 
    var serialized = new JavaScriptSerializer().Serialize(hash); 
    return serialized; 
} 

disclaimer2 Questo è un mezzo fino alla fine (dal momento che lei ha chiesto la questione) - Per un'applicazione qualità della produzione avrei una ben definita interfaccia in modo che fosse chiaro ciò che veniva richiesto e inviato sul filo.

+0

Sì, restituire una stringa è un punto valido. Tuttavia, dover serializzare manualmente in ciascun metodo è inaccettabile. In realtà sto pensando a una specie di involucro per questo. Ho solo pensato che l'utilizzo della serializzazione DataContract sarebbe stato più veloce in quanto parte del framework. È? – tishma

+0

Tuttavia, il codice Javascript dovrà sapere esattamente cosa viene restituito da una richiesta dal prototipo alla produzione, e non riesco a vedere quanto la tipizzazione rigorosa in .NET possa essere d'aiuto. Può esso? – tishma

+1

tishma, non ho alcuna metrica sul fatto che 'DataContract' sarebbe più veloce. Tuttavia non mi sorprenderebbe se serializzare usando un serializzatore JSON di terze parti (es. Json.NET) e restituire una stringa potesse essere più veloce di ottenere la serializzazione di WCF. E no, la tipizzazione rigorosa non aiuterà con il lato javascript delle cose, ma con una tipizzazione rigorosa puoi guardare la tua "API pubblica" e vedere esattamente ciò che esponi invece di esporre un "dizionario " che restituisce chi sa cosa. – wal

Problemi correlati