Serializar um objeto para string

311

Eu tenho o seguinte método para salvar um objeto em um arquivo:

// 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();
}

Confesso que não o escrevi (apenas o convertei em um método de extensão que utilizou um parâmetro de tipo).

Agora, preciso que ele me devolva o xml como uma string (em vez de salvá-lo em um arquivo). Estou investigando, mas ainda não descobri.

Eu pensei que isso poderia ser realmente fácil para alguém familiarizado com esses objetos. Se não, eu vou descobrir isso eventualmente.

Vaccano
fonte

Respostas:

530

Use um em StringWritervez de 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();
    }
}

Observe que é importante usar em toSerialize.GetType()vez do typeof(T)construtor XmlSerializer: se você usar o primeiro, o código cobrirá todas as subclasses possíveis de T(que são válidas para o método), enquanto o uso do último falhará ao passar um tipo derivado T. Aqui está um link com algum código de exemplo que motiva essa instrução, ao XmlSerializerlançar um Exceptionquando typeof(T)é usado, porque você passa uma instância de um tipo derivado para um método que chama SerializeObject que é definido na classe base do tipo derivado: http: // ideone .com / 1Z5J1 .

Além disso, o Ideone usa o Mono para executar código; o real que Exceptionvocê obteria usando o tempo de execução do Microsoft .NET tem um diferente Messagedo mostrado no Ideone, mas falha da mesma forma.

dtb
fonte
2
@ JohnSaunders: ok, é uma boa ideia mover essa discussão sobre o Meta. Aqui está o link para a pergunta que acabei de publicar no Meta Stack Overflow sobre esta edição .
Fulvio
27
@casperOne Pessoal, por favor, parem de mexer com a minha resposta. O ponto é usar o StringWriter em vez do StreamWriter, tudo o mais não é relevante para a questão. Se você quiser discutir detalhes como typeof(T) versus toSerialize.GetType(), faça-o, mas não na minha resposta. Obrigado.
DTB
9
@dtb Desculpe, mas o Stack Overflow é editado em colaboração . Além disso, essa resposta específica foi discutida na meta , portanto a edição permanece. Se você não concordar, responda a essa postagem na meta sobre por que você acha que sua resposta é um caso especial e não deve ser editada em colaboração.
casperOne
2
Codewise, este é o exemplo mais curto que eu já vi. +1
froggythefrog
13
StringWriter implementa IDisposable, portanto, deve ser colocado em um bloco using.
TrueWill 4/03/2014
70

Eu sei que isso não é realmente uma resposta para a pergunta, mas com base no número de votos para a pergunta e a resposta aceita, suspeito que as pessoas estejam realmente usando o código para serializar um objeto em uma string.

O uso da serialização XML adiciona lixo de texto extra desnecessário à saída.

Para a seguinte classe

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

gera

<?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>

A melhor solução é usar a serialização JSON (uma das melhores é o Json.NET ). Para serializar um objeto:

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

Para desserializar um objeto:

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

A sequência JSON serializada seria semelhante a:

{"UserId":0}
xhafan
fonte
4
Nesse caso, você está certo, mas viu documentos XML grandes e documentos JSON grandes. O documento JSON é dificilmente legível. O "lixo" de que você está falando, como os espaços para nome, pode ser suprimido. O XML gerado pode ser tão limpo quanto JSON, mas é SEMPRE mais legível como JSON. A legibilidade é uma grande vantagem acima do JSON.
Herman Van Der Blom
2
Se você pesquisar on-line "json online parser", encontrará alguns analisadores json on-line que podem formatar a string json de maneira mais legível por humanos.
Xhafan # 8/17
9
@HermanVanDerBlom XML mais legível que JSON? O que você está fumando no mundo? Essa é uma das maiores vantagens do JSON sobre XML: é muito mais fácil de ler devido à maior relação sinal / ruído. Simplificando, com JSON o conteúdo não está se afogando na sopa de tags!
Mason Wheeler
63

Serializar e desserializar (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);
    }
ADMITEM
fonte
15
+1 por mostrar também como desserializar, ao contrário de todas as outras respostas. Obrigado!
deadlydog
6
Uma pequena alteração, porém, seria retornar T em vez de objeto e converter o objeto retornado para T na função DeserializeObject. Dessa forma, o objeto fortemente digitado é retornado em vez de um objeto genérico.
deadlydog
Obrigado @deadlydog, eu consertei.
ADM-IT
3
O TextWriter possui uma função Dispose () que deve ser chamada. Então você está esquecendo as instruções Using.
Herman Van Der Blom
38

Código Nota de segurança

Em relação à resposta aceita , é importante usar em toSerialize.GetType()vez de typeof(T)no XmlSerializerconstrutor: se você usar a primeira, o código cobrirá todos os cenários possíveis, enquanto o último falha algumas vezes.

Aqui está um link com algum código de exemplo que motiva essa instrução, ao XmlSerializerlançar uma exceção quando typeof(T)é usado, porque você passa uma instância de um tipo derivado para um método que chama SerializeObject<T>()definido na classe base do tipo derivado: http: // ideone .com / 1Z5J1 . Observe que o Ideone usa o Mono para executar o código: a exceção real que você obteria usando o tempo de execução do Microsoft .NET tem uma mensagem diferente da mostrada no Ideone, mas falha da mesma forma.

Por uma questão de exaustividade, posto aqui o exemplo de código completo aqui para referência futura, caso Ideone (onde publiquei o código) fique indisponível no 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
fonte
12
Você também deve fazer using (StringWriter textWriter = new StringWriter() {}o fechamento / descarte adequado do objeto.
Amigável
Concordo plenamente com você @Amicable! Eu simplesmente tentei manter meu exemplo de código o mais próximo possível do OP, para destacar meu ponto, que é sobre tipos de objetos. De qualquer forma é bom lembrar alguém que a usingdeclaração é o melhor amigo tanto para nós e para os nossos queridos IDisposableimplementação de objetos;)
Fulvio
"Métodos de extensão permitem" adicionar "métodos a tipos existentes sem criar um novo tipo derivado, recompilar ou modificar o tipo original". msdn.microsoft.com/en-us/library/bb383977.aspx
Adrian
12

Meu 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
fonte
+1 para usar XmlWriterSettings (). Eu queria que meu XML serializado não desperdice espaço com as coisas bonitas de impressão e a configuração Indent = false e NewLineOnAttributes = false fizeram o trabalho.
amigos estão dizendo sobre lee Richardson
Obrigado @LeeRichardson - eu precisava fazer exatamente o oposto, também o XmlWriter sob .net usa como padrão o UTF16, que não é o que eu estava escrevendo também.
oPless
usar essa combinação de fluxo de memória e obtê-lo através da codificação GetString incluirá o preâmbulo / BOM como o primeiro caractere em sua string. Veja também stackoverflow.com/questions/11701341/…
Jamee
@Jamee "Encoding = UTF8Encoding (false)" significa não escrever a lista técnica, conforme docs.microsoft.com/en-us/dotnet/api/… ... isso mudou o comportamento desde .net4?
oPless
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
fonte
1

Não consegui usar o método JSONConvert sugerido por xhafan

No .Net 4.5, mesmo após adicionar a referência de montagem "System.Web.Extensions", eu ainda não consegui acessar o JSONConvert.

No entanto, depois de adicionar a referência, você pode obter a mesma string usando:

JavaScriptSerializer js = new JavaScriptSerializer();
string jsonstring = js.Serialize(yourClassObject);
Thomas Tiveron
fonte
2
A classe JSONConvert está no espaço de nome NewtonSoft.Json. Ir para o gerenciador de pacotes em você VS e depois baixar pacote NewtonSoft.Json
Amir Shrestha
1

Senti que precisava compartilhar esse código manipulado com a resposta aceita - como não tenho reputação, não posso 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
fonte
Sei que isso é antigo, mas como você deu uma resposta muito boa, adicionarei um pequeno comentário, como se eu fizesse uma revisão de código em um PR: você deve ter restrições em T ao usar genéricos. Ajuda a manter as coisas organizadas, e nem todo objeto em uma base de código e estruturas referenciadas se presta à serialização
Frank R. Haugen
-1

Em alguns casos raros, convém implementar sua própria serialização de String.

Mas isso provavelmente é uma péssima idéia, a menos que você saiba o que está fazendo. (por exemplo, serializando para E / S com um arquivo em lotes)

Algo assim faria o truque (e seria fácil editar manualmente / lote), mas tome cuidado para que mais algumas verificações sejam feitas, como se o nome não contenha uma nova linha.

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
fonte
-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
fonte