it-swarm.dev

Serializacja obiektu jako UTF-8 XML w .NET

Usunięto właściwe usuwanie obiektów dla zwięzłości, ale jestem zszokowany, jeśli jest to najprostszy sposób zakodowania obiektu w pamięci jako UTF-8. Musi być łatwiejszy sposób, prawda?

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();
101
Garry Shutler

Twój kod nie zapisuje UTF-8 do pamięci, gdy odczytujesz go ponownie w ciągu, więc nie jest już w UTF-8, ale z powrotem w UTF-16 (chociaż najlepiej jest rozważyć ciągi na wyższym poziomie niż wszelkie kodowanie, z wyjątkiem przypadków, gdy jest to do tego zmuszone).

Aby uzyskać rzeczywiste oktety UTF-8, możesz użyć:

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

Pominąłem to samo, co ty zostawiłeś. Nieco sprzyjam następującym (z pozostawieniem normalnego usuwania):

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

Co jest w tej samej mierze złożonością, ale pokazuje, że na każdym etapie istnieje rozsądny wybór, aby zrobić coś innego, z których najbardziej paląca jest serializacja do miejsca innego niż pamięć, na przykład do pliku, TCP/IP strumień, baza danych itp. Podsumowując, to wcale nie jest tak szczegółowe.

52
Jon Hanna

Nie, możesz użyć StringWriter, aby pozbyć się pośredniego MemoryStream. Jednak aby wymusić to na XML, musisz użyć StringWriter, która przesłania właściwość Encoding:

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

Lub jeśli nie używasz jeszcze C # 6:

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

Następnie:

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

Oczywiście możesz zrobić Utf8StringWriter do bardziej ogólnej klasy, która akceptuje dowolne kodowanie w swoim konstruktorze - ale z mojego doświadczenia wynika, że ​​UTF-8 jest zdecydowanie najczęściej wymaganym „niestandardowym” kodowaniem dla StringWriter :)

Teraz, jak mówi Jon Hanna, nadal będzie to UTF-16 wewnętrznie, ale prawdopodobnie przekażesz go do czegoś innego w pewnym momencie, aby przekonwertować go na dane binarne ... w that point możesz użyć powyższego ciągu, przekonwertować go na bajty UTF-8 i wszystko będzie dobrze - ponieważ deklaracja XML będzie określać „utf-8” jako kodowanie.

EDYCJA: Krótki, ale kompletny przykład pokazujący to działanie:

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

Wynik:

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

Zwróć uwagę na deklarowane kodowanie „utf-8”, które, jak sądzę, było tym, czego chcieliśmy.

256
Jon Skeet

Bardzo dobra odpowiedź przy użyciu dziedziczenia, pamiętaj tylko o zastąpieniu inicjalizatora

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

Znalazłem ten post na blogu, który bardzo dobrze wyjaśnia problem i definiuje kilka różnych rozwiązań:

(martwy link usunięty)

Zdecydowałem się na pomysł, że najlepszym sposobem na to jest całkowite pominięcie deklaracji XML w pamięci. Właściwie to is UTF-16 w tym momencie i tak, ale deklaracja XML nie wydaje się znacząca, dopóki nie zostanie zapisana w pliku z określonym kodowaniem; i nawet wtedy deklaracja nie jest wymagana. Przynajmniej nie wydaje się, że to przełamuje deserializację.

Jak wspomniał @Jon Hanna, można to zrobić za pomocą XmlWriter utworzonego w następujący sposób:

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