2012-04-26 33 views
9

Planeo construir un servicio WCF que devuelva objetos de diccionario genéricos serializados a JSON. Desafortunadamente, la serialización falla, ya que el objeto es potencialmente siempre diferente. KnownTypes no puede ayudar, porque el tipo de propiedad es Dictionary, y no puedo decir KnownType, ya que la clase siempre será diferente.¿Puedo serializar Dictionary <string, object> usando el serializador DataContract?

¿Alguna idea si es posible serializar un 'tipo desconocido'?

No me importa especificar DataContract/DataMember para cada una de mis clases, pero (al menos para la versión de prototipo) no quiero tener tipos fuertes para cada respuesta. Al cliente Javascript simplemente no le importa.

¿Qué hay de las clases anónimas?

+0

Me alegra que la gente ya esté así. Por favor, no se moleste en responder con argumentos sobre cómo WCF no fue hecho para eso, etc. Le agradecería si simplemente nos puede informar si hay una manera conocida o si usted ha estado allí y se dio por vencido por razones válidas (qué ¿razones?). – tishma

Respuesta

7

NOTA: He entrado en muchos detalles sobre el JavaScriptSerializer al comienzo de mi respuesta, si solo quiere leer sobre la resolución del problema de tipo conocido mencionado en la pregunta original, salte hasta el final de la respuesta.

Rendimiento

Sobre la base de la benchmarks corría, el JavaScriptSerializer es el mucho más lento que las otras alternativas y puede tomar 2 veces el tiempo para serializar/deserializar un objeto en comparación con DataContractSerializer.

No hay necesidad de un tipo conocido

Dicho esto, el JavaScriptSerializer es más flexible ya que no requiere especificar los tipos conocidos '' antes de tiempo, y el JSON serializado es más limpio, al menos en el caso de los diccionarios (véanse los ejemplos here).

La otra cara de esa flexibilidad en torno a los tipos conocidos es que no podrá deserializar la misma cadena JSON al tipo original.Por ejemplo, supongamos que tengo una clase simple Person:

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

    public int Age { get; set; } 
} 

Y si puedo crear una instancia de Dictinoary<string, object> y añadir una instancia de la clase Person a ella antes de que la serialización:

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); 

Voy Obtenga el siguiente JSON {"me":{"Name":"Yan","Age":30}} que es muy limpio pero no contiene ningún tipo de información. Así supuesta si tiene dos clases con las mismas definiciones de miembros o si Person es una subclase sin introducir cualquier miembro adicional:

public class Employee : Person 
{ 
} 

entonces simplemente no hay manera para que el serializador para poder garantizar que el JSON {"Name":"Yan","Age":30} se puede deserializar al tipo correcto.

Si deserializar {"me":{"Name":"Yan","Age":30}} utilizando el JavaScriptSerializer, en el diccionario que vuelva el valor asociado con el "yo" no es una instancia de Person sino un Dictionary<string, object> lugar, una bolsa de propiedades simple.

Si desea obtener una instancia Person atrás, usted podría (aunque lo más probable es que nunca quiere!) Convertir ese Dictionary<string, object> utilizando el método ConvertToType ayudante:

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

Por otro lado, si no necesita preocuparse por deserializar esos JSON en el tipo correcto y la serialización JSON no es un cuello de botella de rendimiento (perfile su código y descubra cuánto tiempo de CPU se gasta en serialización si aún no lo ha hecho) entonces diría solo use JavaScriptSerializer.

La inyección de tipo conocido

si, al final del día, usted todavía tiene que utilizar DataContractSerializer y la necesidad de inyectar esos KnownTypes, aquí hay dos cosas que puede probar.

1) Pase la matriz de tipos conocidos al DataContractSerializer constructor.

2) Pase una subclase de DataContractResolver (con los medios para localizar los tipos de interés) a DataContractSerializer constructor

Se puede crear un 'registro de tipo conocido' del tipo que realiza un seguimiento de los tipos que se puede ser agregado al diccionario, y si el control de todos los tipos que usted necesita inyectarse a DataContractSerializer, puede intentar lo más sencillo:

  1. crear una clase con métodos estáticos KnownTypeRegister añadir un tipo de a la lista de tipos conocidos:

    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. Añadir un constructor estático que registra los tipos con el registro:

    [DataContract] 
    public class Person 
    { 
        static Person() 
        { 
         KnownTypeRegister.Add(typeof(Person)); 
        } 
        [DataMember] 
        public string Name { get; set; } 
        [DataMember] 
        public int Age { get; set; } 
    }
  3. Obtener la matriz de tipos conocidos del registro cuando se construye el serializador:

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

Más opciones dinámicas/mejores son posibles e, pero también son más difíciles de implementar, si desea obtener más información sobre la resolución dinámica conocida, consulte el artículo de MSDN de Juval Lowy sobre el tema here. Además, this blog post de Carlos Figueira también entra en detalles sobre técnicas más avanzadas, tales como la generación dinámica de los tipos, ¡vale la pena leerlos mientras habla sobre el tema!

+0

Antes que nada, gracias por la respuesta detallada. Realmente parece que vale la pena investigar a lo largo de las líneas. También aprendí sobre ServiceStack en tu blog, y definitivamente suena mucho más sencillo construir un servicio REST JSON que cualquier otra cosa. – tishma

1

descargo de responsabilidad No lo aconsejo exponer object en un punto final WCF; Aunque esto parece 'flexible', esta no es una buena idea ya que no ha especificado qué tipo de información le servirá su servicio.

y ahora una respuesta

Si su llamada WCF está siendo consumida por una llamada Ajax y como usted dice Javascript client just doesn't care. entonces por qué no hacer su llamada WCF simplemente devuelva una cadena? A continuación, la parte interna de su llamada WCF pueden serializar el Dictionary<string, object> utilizando el 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 Este es un medio para el final (ya que hizo la pregunta) - Para una aplicación de calidad de la producción tendría una bien definida interfaz, por lo que estaba claro lo que se solicitaba y se enviaba por el cable.

+0

Sí, devolver una cadena es un punto válido. Sin embargo, tener que serializar manualmente en cada método es inaceptable. De hecho, estoy pensando en algún tipo de envoltorio para eso. Simplemente sentí que usar la serialización de DataContract sería más rápido ya que es parte del framework. ¿Lo es? – tishma

+0

Sin embargo, el código Javascript tendrá que saber exactamente qué devuelve una solicitud desde el prototipo hasta la producción, y no puedo ver qué tan estricto mecanografía en .NET puede ayudar con eso. ¿Puede? – tishma

+1

tishma, no tengo ninguna métrica sobre si 'DataContract' sería más rápido. Sin embargo, no me sorprendería si serializar utilizando un serializador JSON de terceros (por ejemplo, Json.NET) y devolver una cadena podría ser más rápido que conseguir que WCF serialice. Y no, el tipeo estricto no ayudará con el lado de JavaScript de las cosas, pero con el tipado estricto puede ver su 'API pública' y ver exactamente lo que expone en lugar de exponer un 'Diccionario ' que devuelve quién sabe que – wal

Cuestiones relacionadas