2011-02-14 10 views
12

Tengo un montón de diferentes clases de DTO. Se están serializando en una cadena XML en un punto y se pasan al lado del cliente de la aplicación web. Ahora cuando el cliente recupera una cadena XML, necesito deserializarla de nuevo a una instancia de la clase DTO que representa. El problema es que quiero que sea genérico y posiblemente una función que tome una cadena xml y escuche un objeto de un tipo. Algo así como un largo de estas líneas:Deserialización genérica de una cadena xml

public sometype? Deserialize (string xml) 
{ 
//some code here 
return objectFromXml; 
} 

EDITAR: ¡Ejemplo horrible! ¡Simplemente me contradiqué!

no puede hacer lo siguiente:

Person person = Deserialize(personXmlStringFromClient); 

porque no sé que personXmlStringFromClient es una representación de la instancia de objeto Persona DTO.

No sé qué objeto serializado me es dado y ese parece ser mi problema aquí. He estado leyendo acerca de la reflexión y otras técnicas que implican pegar el tipo en el xml para que el deserializador sepa qué hacer con él. Parece que no puedo unir todo en una sola pieza de trabajo. Además, en la mayoría de los ejemplos, el autor sabe qué tipo habrá después de la deserialización. Cualquier sugerencia es bienvenida! Si necesito hacer algo especial con el proceso de serialización, por favor compártelo también.

Respuesta

20

Puede utilizar un genérico:

public T Deserialize<T>(string input) 
     where T : class 
    { 
     System.Xml.Serialization.XmlSerializer ser = new System.Xml.Serialization.XmlSerializer(typeof(T)); 

     using (StringReader sr = new StringReader(input)) 
      return (T)ser.Deserialize(sr); 
    } 

Si no sabe qué tipo será, que se supone que tiene un número fijo de tipos posibles, y usted podría intentar deserializar a cada uno hasta que no encuentras una excepción No es genial, pero funcionaría.

O bien, podría inspeccionar el inicio del xml para el nombre del objeto externo y, con suerte, poder determinar el tipo desde allí. Esto podría variar según el aspecto del xml.

Editar: Según su edición, si la persona que llama sabe el tipo que están pasando, ¿podrían proporcionar el nombre de tipo completo como una cadena como un parámetro adicional para el servicio?

Si es así, usted podría hacer esto:

Type t = Type.GetType(typeName); 

y cambia el método de Deserialize que ser así:

public object Deserialize(string input, Type toType) 
{ 
    System.Xml.Serialization.XmlSerializer ser = new System.Xml.Serialization.XmlSerializer(toType); 

    using (StringReader sr = new StringReader(input)) 
     return ser.Deserialize(sr); 
} 

Sin embargo, esto sólo se consigue una object ... Si todos los tipos en cuestión implementan una interfaz común, puede deserializar como arriba pero cambiar el tipo de retorno a la interfaz (y emitirlo a la declaración de devolución)

+0

¡Lo siento! Me confundí a mí mismo. No puedo usar tu función porque no sé qué es T. – Dimskiy

+0

@Dimskiy: ¿Sabe qué T * podría * ser (es decir, una lista de tipos potenciales)? –

+0

Si pudiera obtener el tipo como cadena, ¿cómo convertiría la cadena "Persona" en un tipo real? – Dimskiy

0

How abou t haciendo una función de "puerta principal" no genérica cuyo propósito es resolver esto? La mayoría de los esquemas XML usan el nombre del objeto, o un facsímil razonable, como la etiqueta más externa para el objeto.

+0

Obtuve unas 60 DTO y posiblemente más en el futuro :( – Dimskiy

0

Si no le importa genéricos:

public static T DeserializeFromString<T>(string value) 
{ 
    T outObject; 
    XmlSerializer deserializer = new XmlSerializer(typeof(T)); 
    StringReader stringReader = new StringReader(value); 
    outObject = (T)deserializer.Deserialize(stringReader); 
    stringReader.Close(); 
    return outObject; 
} 

Editar: Si usted no sabe qué tipo de objeto XML se traducirá en que no se puede volver que no sea un objeto nada. Por supuesto, podrías probar qué tipo de objeto acabas de obtener.Una forma de hacerlo sería pasar todos los tipos de objetos deserializados XmlSerializer.

public static object DeserializeFromString(string value, Type[] types) 
{ 
    XmlSerializer deserializer = new XmlSerializer(typeof(object), types); 
    StringReader stringReader = new StringReader(value); 
    object outObject = deserializer.Deserialize(stringReader); 
    stringReader.Close(); 
    return outObject; 
} 

Usando este método, se asumirá que el objeto quedó en caja (que debe serializar la misma manera), que le da XML como esto:

<object xsi:type="Person"> 
    ... 
</object> 

(Si usted tiene muchos tipos de Pass puede utilizar la reflexión para conseguirlos, por ejemplo, usando algo como Assembly.GetExecutingAssembly().GetTypes())

0

si tiene rutina personalizada serialización/deserialización para cada tipo, podría utilizar algo como esto

public T Deserialize <T>(string xml) 
{ 
    if(typeof(T) == typeof(Person)) 
    { 
     // deserialize and return Person instance 
    } 
    else if(typeof(T) == typeof(Address) 
    { 
     // deserialize and return Address instance 
    } 
    ... 
    ... 
    ... 
} 

Y puede llamar

Person p = Deserialize<Person>(personXmlStringFromClient); 
+0

Obtuve entre 50 y 60 DTO con la posibilidad de agregar una nueva en algún momento en el futuro. – Dimskiy

1

Forget genéricos. ¿Qué sucede si no conoce el tipo de devolución? Si está utilizando Visual C# 2010 o posterior, esta es la belleza de la nueva palabra clave dynamic. A continuación se muestra una clase de serializador de ejemplo que escribí, y el uso de muestras que solo toma la cadena XML e intenta analizarlo para un tipo general System, devolviendo un valor de salida en caso de éxito. Probablemente pueda ampliarlo para otros tipos más personalizados que tenga, pero probablemente necesiten tener un constructor predeterminado y probablemente necesite hacer más análisis para obtener el nombre del tipo y luego obtener la ruta a él en su ensamblaje. . Es un poco complicado, pero una vez que conoces cómo funciona el siguiente código, comienza a abrir un montón de posibilidades. ¿No es esto exactamente lo que estás buscando? Su pregunta pregunta cómo recuperar un objeto de dicho tipo sin saber ese tipo (tenga en cuenta, sin embargo, que todavía debe tener una definición de ese tipo en su código para deserializarlo). Dejame explicar. Si nos fijamos en cómo se usó assemblyFormatter en el código siguiente, verá que es más complicado para un tipo que usted mismo define, como struct o enum por ejemplo, porque para estos tipos debe pasar assemblyFormatter como myObject.GetType().FullName. Esa es la cadena que usa el activador para llamar al constructor predeterminado de su tipo para crearlo y así poder crear un serializador fuera de él; Básicamente, se reduce a la complejidad de tener que saber en su ensamble esa definición de tipo para poder deserializarla.

using System; 
using System.Collections.Generic; 
using System.IO; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 
using System.Xml.Linq; 
using System.Xml.Serialization; 

namespace DynamicSerializer 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      bool myObject = true; 
      // There are a bunch of other examples you can try out: 
      // string myObject = "Hello, world."; 
      // long myObject = 1000; 
      // int myObject = 100; 
      string mySerializedObject; 
      if (Serializer.TrySerialize(myObject, out mySerializedObject)) 
      { 
       Console.WriteLine("Serialized {0} as {1}.", myObject, mySerializedObject); 
       dynamic myDeserializedObject; 
       if (Serializer.TryDeserialize(mySerializedObject, out myDeserializedObject)) 
       { 
        Console.WriteLine("Deserialized {0} as {1}.", mySerializedObject, myDeserializedObject); 
       } 
      } 
      Console.ReadLine(); 
     } 

     class Serializer 
     { 
      public static bool TrySerialize(dynamic unserializedObject, out string serializedObject) 
      { 
       try 
       { 
        StringWriter writer = new StringWriter(); 
        XmlSerializer serializer = new XmlSerializer(unserializedObject.GetType()); 
        serializer.Serialize(writer, unserializedObject); 
        serializedObject = writer.ToString(); 
        return true; 
       } 
       catch 
       { 
        serializedObject = null; 
        return false; 
       } 
      } 

      // The assemblyFormatter parameter is normally not passed in. However, it may be passed in for cases where the type is a special case (such as for enumerables or structs) that needs to be passed into the serializer. If this is the case, this value should be passed in as yourObject.GetType().FullName. 
      public static bool TryDeserialize(string serializedObject, out dynamic deserializedObjectOut, string assemblyFormatter = "System.{0}") 
      { 
       try 
       { 
        StringReader reader = new StringReader(serializedObject); 
        XDocument document = XDocument.Load(reader); 
        string typeString = null; 
        // Map the object type to the System's default value types. 
        switch (document.Root.Name.LocalName) 
        { 
         case "string": 
          typeString = "String"; 
          break; 
         case "dateTime": 
          typeString = "DateTime"; 
          break; 
         case "int": 
          typeString = "Int32"; 
          break; 
         case "unsignedInt": 
          typeString = "UInt32"; 
          break; 
         case "long": 
          typeString = "Int64"; 
          break; 
         case "unsignedLong": 
          typeString = "UInt64"; 
          break; 
         case "boolean": 
          typeString = "Boolean"; 
          break; 
         case "double": 
          typeString = "Double"; 
          break; 
         case "float": 
          typeString = "Single"; 
          break; 
         case "decimal": 
          typeString = "Decimal"; 
          break; 
         case "char": 
          typeString = "Char"; 
          break; 
         case "short": 
          typeString = "Int16"; 
          break; 
         case "unsignedShort": 
          typeString = "UInt16"; 
          break; 
         case "byte": 
          typeString = "SByte"; 
          break; 
         case "unsignedByte": 
          typeString = "Byte"; 
          break; 
        } 
        if (assemblyFormatter != "System.{0}") 
        { 
         typeString = document.Root.Name.LocalName; 
        } 
        if (typeString == null) 
        { 
         // The dynamic object's type is not supported. 
         deserializedObjectOut = null; 
         return false; 
        } 
        if (typeString == "String") 
        { 
         // System.String does not specify a default constructor. 
         XmlSerializer serializer = new XmlSerializer(typeof(String)); 
         reader = new StringReader(serializedObject); 
         deserializedObjectOut = serializer.Deserialize(reader); 
        } 
        else 
        { 
         object typeReference; 
         if (assemblyFormatter != "System.{0}") 
         { 
          typeReference = Activator.CreateInstance(Type.GetType(assemblyFormatter)); 
         } 
         else 
         { 
          typeReference = Activator.CreateInstance(Type.GetType(String.Format(assemblyFormatter, typeString))); 
         } 
         XmlSerializer serializer = new XmlSerializer(typeReference.GetType()); 
         reader = new StringReader(serializedObject); 
         deserializedObjectOut = serializer.Deserialize(reader); 
        } 
        return true; 
       } 
       catch 
       { 
        deserializedObjectOut = null; 
        return false; 
       } 
      } 
     } 
    } 
} 
Cuestiones relacionadas