it-swarm.dev

Serializando um objeto como XML UTF-8 no .NET

Descarte adequado do objeto removido por brevidade, mas estou chocado se esta é a maneira mais simples de codificar um objeto como UTF-8 na memória. Tem que haver uma maneira mais fácil não existe?

var serializer = new XmlSerializer(typeof(SomeSerializableObject));

var memoryStream = new MemoryStream();
var streamWriter = new StreamWriter(memoryStream, System.Text.Encoding.UTF8);

serializer.Serialize(streamWriter, entry);

memoryStream.Seek(0, SeekOrigin.Begin);
var streamReader = new StreamReader(memoryStream, System.Text.Encoding.UTF8);
var utf8EncodedXml = streamReader.ReadToEnd();
98
Garry Shutler

Seu código não recebe o UTF-8 na memória enquanto você o lê de volta em uma string novamente, então não está mais em UTF-8, mas em UTF-16 (embora idealmente é melhor considerar strings em um nível mais alto do que qualquer codificação, exceto quando forçado a fazê-lo).

Para obter os octetos reais do UTF-8, você pode usar:

var serializer = new XmlSerializer(typeof(SomeSerializableObject));

var memoryStream = new MemoryStream();
var streamWriter = new StreamWriter(memoryStream, System.Text.Encoding.UTF8);

serializer.Serialize(streamWriter, entry);

byte[] utf8EncodedXml = memoryStream.ToArray();

Eu deixei de fora a mesma disposição que você deixou. Eu prefiro um pouco o seguinte (com o descarte normal deixado):

var serializer = new XmlSerializer(typeof(SomeSerializableObject));
using(var memStm = new MemoryStream())
using(var  xw = XmlWriter.Create(memStm))
{
  serializer.Serialize(xw, entry);
  var utf8 = memStm.ToArray();
}

Que tem a mesma quantidade de complexidade, mas mostra que em cada estágio há uma escolha razoável de fazer outra coisa, a mais urgente é serializar para outro lugar que não seja a memória, como um arquivo, TCP/IP fluxo, banco de dados, etc. Ao todo, não é realmente tão detalhado.

51
Jon Hanna

Não, você pode usar um StringWriter para se livrar do intermediário MemoryStream. No entanto, para forçá-lo em XML, você precisa usar um StringWriter que substitui a propriedade Encoding:

public class Utf8StringWriter : StringWriter
{
    public override Encoding Encoding => Encoding.UTF8;
}

Ou se você não estiver usando o C # 6 ainda:

public class Utf8StringWriter : StringWriter
{
    public override Encoding Encoding { get { return Encoding.UTF8; } }
}

Então:

var serializer = new XmlSerializer(typeof(SomeSerializableObject));
string utf8;
using (StringWriter writer = new Utf8StringWriter())
{
    serializer.Serialize(writer, entry);
    utf8 = writer.ToString();
}

Obviamente, você pode tornar Utf8StringWriter em uma classe mais geral que aceita qualquer codificação em seu construtor - mas, na minha experiência, o UTF-8 é de longe a codificação "customizada" mais comumente requerida para um StringWriter :)

Agora, como Jon Hanna diz, isso ainda será UTF-16 internamente, mas presumivelmente você vai passá-lo para outra coisa em algum momento, para convertê-lo em dados binários ... em que ponto você pode usar a string acima, convertê-la em bytes UTF-8 e tudo ficará bem - porque a declaração XML especificará "utf-8" como codificação.

EDIT: Um exemplo curto, mas completo para mostrar isso funcionando:

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

public class Test
{    
    public int X { get; set; }

    static void Main()
    {
        Test t = new Test();
        var serializer = new XmlSerializer(typeof(Test));
        string utf8;
        using (StringWriter writer = new Utf8StringWriter())
        {
            serializer.Serialize(writer, t);
            utf8 = writer.ToString();
        }
        Console.WriteLine(utf8);
    }


    public class Utf8StringWriter : StringWriter
    {
        public override Encoding Encoding => Encoding.UTF8;
    }
}

Resultado:

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

Observe a codificação declarada de "utf-8", que é o que queríamos, acredito.

248
Jon Skeet

Muito boa resposta usando herança, lembre-se de substituir o inicializador

public class Utf8StringWriter : StringWriter
{
    public Utf8StringWriter(StringBuilder sb) : base (sb)
    {
    }
    public override Encoding Encoding { get { return Encoding.UTF8; } }
}
16
Sebastian Castaldi

Eu encontrei este post que explica muito bem o problema e define algumas soluções diferentes:

(link morto removido)

Eu decidi que a melhor maneira de fazer isso é omitir completamente a declaração XML quando estiver na memória. Na verdade, é UTF-16 nesse ponto, de qualquer forma, mas a declaração XML não parece significativa até que tenha sido gravada em um arquivo com uma codificação específica; e mesmo assim a declaração não é necessária. Não parece quebrar a desserialização, pelo menos.

Como o @Jon Hanna menciona, isso pode ser feito com um XmlWriter criado assim:

XmlWriter writer = XmlWriter.Create (output, new XmlWriterSettings() { OmitXmlDeclaration = true });
5
Dave Andersen