Serializar un objeto a una cadena

311

Tengo el siguiente método para guardar un objeto en un archivo:

// Save an object out to the disk
public static void SerializeObject<T>(this T toSerialize, String filename)
{
    XmlSerializer xmlSerializer = new XmlSerializer(toSerialize.GetType());
    TextWriter textWriter = new StreamWriter(filename);

    xmlSerializer.Serialize(textWriter, toSerialize);
    textWriter.Close();
}

Confieso que no lo escribí (solo lo convertí a un método de extensión que tomó un parámetro de tipo).

Ahora necesito que me devuelva el xml como una cadena (en lugar de guardarlo en un archivo). Lo estoy investigando, pero aún no lo he descubierto.

Pensé que esto podría ser realmente fácil para alguien familiarizado con estos objetos. Si no, lo resolveré eventualmente.

Vaccano
fuente

Respuestas:

530

Use un en StringWriterlugar de un StreamWriter:

public static string SerializeObject<T>(this T toSerialize)
{
    XmlSerializer xmlSerializer = new XmlSerializer(toSerialize.GetType());

    using(StringWriter textWriter = new StringWriter())
    {
        xmlSerializer.Serialize(textWriter, toSerialize);
        return textWriter.ToString();
    }
}

Tenga en cuenta que es importante usarlo en toSerialize.GetType()lugar del typeof(T)constructor XmlSerializer: si usa el primero, el código cubre todas las subclases posibles T(que son válidas para el método), mientras que usar el último fallará al pasar un tipo derivado de T. Aquí hay un enlace con un código de ejemplo que motivan esta declaración, con XmlSerializerlanzar una Exceptioncuando typeof(T)se utiliza, debido a que pase una instancia de un tipo derivado a un método que llama SerializeObject que se define en la clase base del tipo derivado: http: // Ideone .com / 1Z5J1 .

Además, Ideone usa Mono para ejecutar código; el Exceptiontiempo de ejecución real de Microsoft .NET es diferente al Messageque se muestra en Ideone, pero falla de todos modos.

dtb
fuente
2
@ JohnSaunders: ok, es una buena idea mover esta discusión sobre Meta. Aquí está el enlace a la pregunta que acabo de publicar en Meta Stack Overflow con respecto a esta edición .
Fulvio
27
@casperOne Chicos, dejen de jugar con mi respuesta. El punto es usar StringWriter en lugar de StreamWriter, todo lo demás no es relevante para la pregunta. Si desea analizar detalles como typeof(T) versus toSerialize.GetType(), hágalo, pero no en mi respuesta. Gracias.
dtb
99
@dtb Lo sentimos, pero Stack Overflow se edita en colaboración . Además, esta respuesta específica se ha discutido en meta , por lo que la edición se mantiene. Si no está de acuerdo, responda a esa publicación en meta sobre por qué cree que su respuesta es un caso especial y no debe editarse en colaboración.
casperOne
2
Codewise, este es el ejemplo más corto que he visto. +1
froggythefrog
13
StringWriter implementa IDisposable, por lo que debe encerrarse en un bloque de uso.
TrueWill
70

Sé que esto no es realmente una respuesta a la pregunta, pero según el número de votos para la pregunta y la respuesta aceptada, sospecho que las personas realmente están usando el código para serializar un objeto en una cadena.

El uso de la serialización XML agrega basura de texto adicional innecesaria a la salida.

Para la siguiente clase

public class UserData
{
    public int UserId { get; set; }
}

genera

<?xml version="1.0" encoding="utf-16"?>
<UserData xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
   <UserId>0</UserId>
</UserData>

La mejor solución es usar la serialización JSON (una de las mejores es Json.NET ). Para serializar un objeto:

var userData = new UserData {UserId = 0};
var userDataString = JsonConvert.SerializeObject(userData);

Para deserializar un objeto:

var userData = JsonConvert.DeserializeObject<UserData>(userDataString);

La cadena JSON serializada se vería así:

{"UserId":0}
xhafan
fuente
44
En este caso tiene razón, pero ha visto documentos XML grandes y documentos JSON grandes. Los documentos JSON son apenas legibles. La "basura" de la que está hablando, como los espacios de nombres, se puede suprimir. El XML generado puede ser tan limpio como JSON, pero SIEMPRE es más legible que JSON. La legibilidad es una gran ventaja sobre JSON.
Herman Van Der Blom
2
Si busca en línea "analizador en línea json", encontrará algunos analizadores en línea json que pueden formatear la cadena json de una manera más legible para los humanos.
xhafan
99
@HermanVanDerBlom ¿XML más legible que JSON? ¿Qué estás fumando en el mundo? Esa es una de las ventajas más fuertes de JSON sobre XML: es mucho más fácil de leer debido a la mayor relación señal / ruido. En pocas palabras, ¡con JSON el contenido no se está ahogando en la sopa de etiquetas!
Mason Wheeler
63

Serializar y deserializar (XML / JSON):

    public static T XmlDeserialize<T>(this string toDeserialize)
    {
        XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
        using(StringReader textReader = new StringReader(toDeserialize))
        {      
            return (T)xmlSerializer.Deserialize(textReader);
        }
    }

    public static string XmlSerialize<T>(this T toSerialize)
    {
        XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
        using(StringWriter textWriter = new StringWriter())
        {
            xmlSerializer.Serialize(textWriter, toSerialize);
            return textWriter.ToString();
        }
    }

    public static T JsonDeserialize<T>(this string toDeserialize)
    {
        return JsonConvert.DeserializeObject<T>(toDeserialize);
    }

    public static string JsonSerialize<T>(this T toSerialize)
    {
        return JsonConvert.SerializeObject(toSerialize);
    }
ADMITIR
fuente
15
+1 para mostrar también cómo deserializar, a diferencia de todas las otras respuestas. ¡Gracias!
deadlydog
66
Sin embargo, un cambio menor sería devolver T en lugar de objeto, y convertir el objeto devuelto en T en la función DeserializeObject. De esta forma, se devuelve el objeto fuertemente tipado en lugar de un objeto genérico.
deadlydog
Gracias @deadlydog, lo he arreglado.
ADM-IT
3
TextWriter tiene una función Dispose () que debería llamarse. Entonces estás olvidando las declaraciones de Uso.
Herman Van Der Blom
38

Nota de seguridad del código

Con respecto a la respuesta aceptada , es importante usarla en toSerialize.GetType()lugar de typeof(T)en el XmlSerializerconstructor: si usa la primera, el código cubre todos los escenarios posibles, mientras que usar la última falla a veces.

Aquí hay un enlace con algún código de ejemplo que motiva esta declaración, con XmlSerializeruna excepción cuando typeof(T)se usa, porque pasa una instancia de un tipo derivado a un método que llama SerializeObject<T>()que se define en la clase base del tipo derivado: http: // ideone .com / 1Z5J1 . Tenga en cuenta que Ideone usa Mono para ejecutar código: la excepción real que obtendría con el tiempo de ejecución de Microsoft .NET tiene un mensaje diferente al que se muestra en Ideone, pero falla de todos modos.

En aras de la exhaustividad, publico el ejemplo de código completo aquí para referencia futura, en caso de que Ideone (donde publiqué el código) no esté disponible en el futuro:

using System;
using System.Xml.Serialization;
using System.IO;

public class Test
{
    public static void Main()
    {
        Sub subInstance = new Sub();
        Console.WriteLine(subInstance.TestMethod());
    }

    public class Super
    {
        public string TestMethod() {
            return this.SerializeObject();
        }
    }

    public class Sub : Super
    {
    }
}

public static class TestExt {
    public static string SerializeObject<T>(this T toSerialize)
    {
        Console.WriteLine(typeof(T).Name);             // PRINTS: "Super", the base/superclass -- Expected output is "Sub" instead
        Console.WriteLine(toSerialize.GetType().Name); // PRINTS: "Sub", the derived/subclass

        XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
        StringWriter textWriter = new StringWriter();

        // And now...this will throw and Exception!
        // Changing new XmlSerializer(typeof(T)) to new XmlSerializer(subInstance.GetType()); 
        // solves the problem
        xmlSerializer.Serialize(textWriter, toSerialize);
        return textWriter.ToString();
    }
}
Fulvio
fuente
12
También debe hacerlo using (StringWriter textWriter = new StringWriter() {}para cerrar / desechar el objeto correctamente.
Amistoso
Estoy completamente de acuerdo contigo @Amicable! Simplemente intenté mantener mi código de muestra lo más cerca posible del OP, para resaltar mi punto que se trata de tipos de objetos. De todos modos, es bueno recordar a cualquiera que la usingdeclaración es el mejor amigo tanto para nosotros como para nuestros queridos IDisposableobjetos de implementación;)
Fulvio
"Los métodos de extensión le permiten" agregar "métodos a los tipos existentes sin crear un nuevo tipo derivado, volver a compilar o modificar el tipo original". msdn.microsoft.com/en-us/library/bb383977.aspx
Adrian
12

Mi 2p ...

        string Serialise<T>(T serialisableObject)
        {
            var xmlSerializer = new XmlSerializer(serialisableObject.GetType());

            using (var ms = new MemoryStream())
            {
                using (var xw = XmlWriter.Create(ms, 
                    new XmlWriterSettings()
                        {
                            Encoding = new UTF8Encoding(false),
                            Indent = true,
                            NewLineOnAttributes = true,
                        }))
                {
                    xmlSerializer.Serialize(xw,serialisableObject);
                    return Encoding.UTF8.GetString(ms.ToArray());
                }
            }
        }
oPless
fuente
+1 por usar XmlWriterSettings (). Quería que mi XML serializado no desperdiciara espacio con las cosas bonitas de impresión y la configuración de Indent = false y NewLineOnAttributes = false hizo el trabajo.
Lee Richardson
Gracias @LeeRichardson: necesitaba hacer exactamente lo contrario, también XmlWriter en .net tiene como valor predeterminado UTF16, que tampoco es lo que estaba escribiendo.
Sin
el uso de esta combinación de flujo de memoria y obtenerlo a través de Codificación GetString incluirá el Preámbulo / BOM como el primer carácter en su cadena. Ver también stackoverflow.com/questions/11701341/…
Jamee
@Jamee "Encoding = UTF8Encoding (false)" significa que no escriba la lista de materiales según docs.microsoft.com/en-us/dotnet/api/… ... ¿ha cambiado este comportamiento desde .net4?
Sin
4
public static string SerializeObject<T>(T objectToSerialize)
        {
            System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bf = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
            MemoryStream memStr = new MemoryStream();

            try
            {
                bf.Serialize(memStr, objectToSerialize);
                memStr.Position = 0;

                return Convert.ToBase64String(memStr.ToArray());
            }
            finally
            {
                memStr.Close();
            }
        }

        public static T DerializeObject<T>(string objectToDerialize)
        {
            System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bf = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
            byte[] byteArray = Convert.FromBase64String(objectToDerialize);
            MemoryStream memStr = new MemoryStream(byteArray);

            try
            {
                return (T)bf.Deserialize(memStr);
            }
            finally
            {
                memStr.Close();
            }
        }
teapeng
fuente
1

No pude usar el método JSONConvert sugerido por xhafan

En .Net 4.5, incluso después de agregar la referencia de ensamblaje "System.Web.Extensions", aún no podía acceder a JSONConvert.

Sin embargo, una vez que agregue la referencia, puede imprimir la misma cadena usando:

JavaScriptSerializer js = new JavaScriptSerializer();
string jsonstring = js.Serialize(yourClassObject);
Thomas Tiveron
fuente
2
La clase JSONConvert está en el espacio de nombres NewtonSoft.Json. Vaya al administrador de paquetes en su VS y luego descargue el paquete NewtonSoft.Json
Amir Shrestha
1

Sentí que necesitaba compartir este código manipulado con la respuesta aceptada, ya que no tengo reputación, no puedo comentar ...

using System;
using System.Xml.Serialization;
using System.IO;

namespace ObjectSerialization
{
    public static class ObjectSerialization
    {
        // THIS: (C): /programming/2434534/serialize-an-object-to-string
        /// <summary>
        /// A helper to serialize an object to a string containing XML data of the object.
        /// </summary>
        /// <typeparam name="T">An object to serialize to a XML data string.</typeparam>
        /// <param name="toSerialize">A helper method for any type of object to be serialized to a XML data string.</param>
        /// <returns>A string containing XML data of the object.</returns>
        public static string SerializeObject<T>(this T toSerialize)
        {
            // create an instance of a XmlSerializer class with the typeof(T)..
            XmlSerializer xmlSerializer = new XmlSerializer(toSerialize.GetType());

            // using is necessary with classes which implement the IDisposable interface..
            using (StringWriter stringWriter = new StringWriter())
            {
                // serialize a class to a StringWriter class instance..
                xmlSerializer.Serialize(stringWriter, toSerialize); // a base class of the StringWriter instance is TextWriter..
                return stringWriter.ToString(); // return the value..
            }
        }

        // THIS: (C): VPKSoft, 2018, https://www.vpksoft.net
        /// <summary>
        /// Deserializes an object which is saved to an XML data string. If the object has no instance a new object will be constructed if possible.
        /// <note type="note">An exception will occur if a null reference is called an no valid constructor of the class is available.</note>
        /// </summary>
        /// <typeparam name="T">An object to deserialize from a XML data string.</typeparam>
        /// <param name="toDeserialize">An object of which XML data to deserialize. If the object is null a a default constructor is called.</param>
        /// <param name="xmlData">A string containing a serialized XML data do deserialize.</param>
        /// <returns>An object which is deserialized from the XML data string.</returns>
        public static T DeserializeObject<T>(this T toDeserialize, string xmlData)
        {
            // if a null instance of an object called this try to create a "default" instance for it with typeof(T),
            // this will throw an exception no useful constructor is found..
            object voidInstance = toDeserialize == null ? Activator.CreateInstance(typeof(T)) : toDeserialize;

            // create an instance of a XmlSerializer class with the typeof(T)..
            XmlSerializer xmlSerializer = new XmlSerializer(voidInstance.GetType());

            // construct a StringReader class instance of the given xmlData parameter to be deserialized by the XmlSerializer class instance..
            using (StringReader stringReader = new StringReader(xmlData))
            {
                // return the "new" object deserialized via the XmlSerializer class instance..
                return (T)xmlSerializer.Deserialize(stringReader);
            }
        }

        // THIS: (C): VPKSoft, 2018, https://www.vpksoft.net
        /// <summary>
        /// Deserializes an object which is saved to an XML data string.
        /// </summary>
        /// <param name="toDeserialize">A type of an object of which XML data to deserialize.</param>
        /// <param name="xmlData">A string containing a serialized XML data do deserialize.</param>
        /// <returns>An object which is deserialized from the XML data string.</returns>
        public static object DeserializeObject(Type toDeserialize, string xmlData)
        {
            // create an instance of a XmlSerializer class with the given type toDeserialize..
            XmlSerializer xmlSerializer = new XmlSerializer(toDeserialize);

            // construct a StringReader class instance of the given xmlData parameter to be deserialized by the XmlSerializer class instance..
            using (StringReader stringReader = new StringReader(xmlData))
            {
                // return the "new" object deserialized via the XmlSerializer class instance..
                return xmlSerializer.Deserialize(stringReader);
            }
        }
    }
}

Petteri Kautonen
fuente
Sé que esto es antiguo, pero dado que dio una respuesta realmente buena, agregaré un pequeño comentario, como si hiciera una revisión de código en un RP: debe tener restricciones en T cuando usa genéricos. Ayuda a mantener las cosas ordenadas, y no todos los objetos en una base de código y marcos referenciados se prestan a la serialización
Frank R. Haugen
-1

En algunos casos raros, es posible que desee implementar su propia serialización de cadenas.

Pero eso probablemente sea una mala idea a menos que sepa lo que está haciendo. (p. ej., serialización para E / S con un archivo por lotes)

Algo así haría el truco (y sería fácil de editar a mano / lote), pero tenga cuidado de que se realicen algunas verificaciones más, ya que ese nombre no contiene una nueva línea.

public string name {get;set;}
public int age {get;set;}

Person(string serializedPerson) 
{
    string[] tmpArray = serializedPerson.Split('\n');
    if(tmpArray.Length>2 && tmpArray[0].Equals("#")){
        this.name=tmpArray[1];
        this.age=int.TryParse(tmpArray[2]);
    }else{
        throw new ArgumentException("Not a valid serialization of a person");
    }
}

public string SerializeToString()
{
    return "#\n" +
           name + "\n" + 
           age;
}
satibel
fuente
-1

[VB]

Public Function XmlSerializeObject(ByVal obj As Object) As String

    Dim xmlStr As String = String.Empty

    Dim settings As New XmlWriterSettings()
    settings.Indent = False
    settings.OmitXmlDeclaration = True
    settings.NewLineChars = String.Empty
    settings.NewLineHandling = NewLineHandling.None

    Using stringWriter As New StringWriter()
        Using xmlWriter__1 As XmlWriter = XmlWriter.Create(stringWriter, settings)

            Dim serializer As New XmlSerializer(obj.[GetType]())
            serializer.Serialize(xmlWriter__1, obj)

            xmlStr = stringWriter.ToString()
            xmlWriter__1.Close()
        End Using

        stringWriter.Close()
    End Using

    Return xmlStr.ToString
End Function

Public Function XmlDeserializeObject(ByVal data As [String], ByVal objType As Type) As Object

    Dim xmlSer As New System.Xml.Serialization.XmlSerializer(objType)
    Dim reader As TextReader = New StringReader(data)

    Dim obj As New Object
    obj = DirectCast(xmlSer.Deserialize(reader), Object)
    Return obj
End Function

[C#]

public string XmlSerializeObject(object obj)
{
    string xmlStr = String.Empty;
    XmlWriterSettings settings = new XmlWriterSettings();
    settings.Indent = false;
    settings.OmitXmlDeclaration = true;
    settings.NewLineChars = String.Empty;
    settings.NewLineHandling = NewLineHandling.None;

    using (StringWriter stringWriter = new StringWriter())
    {
        using (XmlWriter xmlWriter = XmlWriter.Create(stringWriter, settings))
        {
            XmlSerializer serializer = new XmlSerializer( obj.GetType());
            serializer.Serialize(xmlWriter, obj);
            xmlStr = stringWriter.ToString();
            xmlWriter.Close();
        }
    }
    return xmlStr.ToString(); 
}

public object XmlDeserializeObject(string data, Type objType)
{
    XmlSerializer xmlSer = new XmlSerializer(objType);
    StringReader reader = new StringReader(data);

    object obj = new object();
    obj = (object)(xmlSer.Deserialize(reader));
    return obj;
}
Brian
fuente