Serialisieren eines Objekts als UTF-8-XML in .NET

112

Die ordnungsgemäße Objektentsorgung wurde der Kürze halber entfernt, aber ich bin schockiert, wenn dies der einfachste Weg ist, ein Objekt als UTF-8 im Speicher zu codieren. Es muss einen einfacheren Weg geben, nicht wahr?

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();
Garry Shutler
quelle
1
Ich bin verwirrt ... ist nicht die Standardcodierung UTF-8?
flq
@flq, ja, der Standardwert ist UTF-8, obwohl es nicht viel ausmacht, da er ihn wieder in einen String zurückliest, ebenso utf8EncodedXmlwie UTF-16.
Jon Hanna
1
@Garry, kannst du das klarstellen, da Jon Skeet und ich unterschiedliche Fragen beantworten. Möchten Sie, dass das Objekt als UTF-8 serialisiert wird, oder möchten Sie eine XML-Zeichenfolge, die sich selbst als UTF-8 deklariert und daher die richtige Deklaration hat, wenn sie später in UTF-8 codiert wird? (In diesem Fall ist es am einfachsten, keine Deklaration zu haben, da dies sowohl für UTF-8 als auch für UTF-16 gilt.)
Jon Hanna
@ Jon Rückblickend gibt es Unklarheiten in meiner Frage. Ich ließ es hauptsächlich zu Debugging-Zwecken in einen String ausgeben. In der Praxis würde ich wahrscheinlich Bytes entweder auf die Festplatte oder über HTTP streamen, wodurch Ihre Antwort für mein Problem direkter relevant wird. Das Hauptproblem, das ich hatte, war die Deklaration von UTF-8 im XML, aber um genauer zu sein, sollte ich die Vermittlung einer Zeichenfolge vermeiden, damit ich UTF-8-Bytes tatsächlich sende / beibehalten kann und nicht plattformabhängig (glaube ich). Codierung.
Garry Shutler

Antworten:

55

Ihr Code speichert UTF-8 nicht im Speicher, wenn Sie es erneut in eine Zeichenfolge zurücklesen. Daher ist es nicht mehr in UTF-8, sondern in UTF-16 (obwohl es idealerweise am besten ist, Zeichenfolgen auf einer höheren Ebene als zu berücksichtigen jede Kodierung, außer wenn sie dazu gezwungen wird).

Um die tatsächlichen UTF-8-Oktette zu erhalten, können Sie Folgendes verwenden:

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

Ich habe die gleiche Entsorgung ausgelassen, die Sie übrig haben. Ich bevorzuge leicht Folgendes (bei normaler Entsorgung):

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

Das ist ungefähr die gleiche Komplexität, zeigt aber, dass es in jeder Phase eine vernünftige Wahl gibt, etwas anderes zu tun, von dem die dringendste darin besteht, an einen anderen Ort als in den Speicher zu serialisieren, z. B. in eine Datei, TCP / IP Stream, Datenbank usw. Alles in allem ist es nicht wirklich so ausführlich.

Jon Hanna
quelle
4
Ebenfalls. Wenn Sie die Stückliste unterdrücken möchten, können Sie verwenden XmlWriter.Create(memoryStream, new XmlWriterSettings { Encoding = new UTF8Encoding(false) }).
Nur
Wenn jemand (wie ich) das XML lesen muss, das wie von Jon erstellt wurde, denken Sie daran, den Speicherstrom auf 0 zu setzen, da sonst eine Ausnahme mit der Meldung "Root-Element fehlt" angezeigt wird. Gehen Sie also folgendermaßen vor: memStm.Position = 0; XmlReader xmlReader = XmlReader.Create (memStm)
Sudhanshu Mishra
276

Nein, Sie können a verwenden StringWriter, um das Zwischenprodukt loszuwerden MemoryStream. Um es jedoch in XML zu erzwingen, müssen Sie ein verwenden, StringWriterdas die EncodingEigenschaft überschreibt :

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

Oder wenn Sie C # 6 noch nicht verwenden:

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

Dann:

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

Natürlich können Sie Utf8StringWritereine allgemeinere Klasse erstellen, die jede Codierung in ihrem Konstruktor akzeptiert - aber meiner Erfahrung nach ist UTF-8 bei weitem die am häufigsten erforderliche "benutzerdefinierte" Codierung für eine StringWriter:)

Nun, wie Jon Hanna sagt, wird dies intern immer noch UTF-16 sein, aber vermutlich werden Sie es irgendwann an etwas anderes übergeben, um es in Binärdaten umzuwandeln ... an diesem Punkt können Sie die obige Zeichenfolge verwenden, Konvertieren Sie es in UTF-8-Bytes, und alles wird gut - da in der XML-Deklaration "utf-8" als Codierung angegeben wird.

EDIT: Ein kurzes, aber vollständiges Beispiel, um diese Arbeitsweise zu zeigen:

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

Ergebnis:

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

Beachten Sie die deklarierte Codierung von "utf-8", die wir meiner Meinung nach wollten.

Jon Skeet
quelle
2
Selbst wenn Sie den Encoding-Parameter in StringWriter überschreiben, werden die geschriebenen Daten an einen StringBuilder gesendet, sodass es sich weiterhin um UTF-16 handelt. Und die Zeichenfolge kann immer nur UTF-16 sein.
Jon Hanna
3
@ Jon: Hast du es versucht? Ich habe und es funktioniert. Es ist die deklarierte Codierung, die hier wichtig ist; Natürlich ist die Zeichenfolge intern immer noch UTF-16, aber das macht keinen Unterschied, bis sie in eine Binärdatei konvertiert wird (die jede Codierung verwenden könnte, einschließlich UTF-8). Die TextWriter.EncodingEigenschaft wird vom XML-Serializer verwendet, um zu bestimmen, welcher Codierungsname im Dokument selbst angegeben werden soll.
Jon Skeet
2
@ Jon: Und was war die deklarierte Kodierung? Nach meiner Erfahrung versuchen solche Fragen wirklich , ein XML-Dokument zu erstellen, das sich selbst als UTF-8 deklariert. Wie Sie sagen, ist es am besten nicht um den Text zu betrachten , um in seine jeder Codierung , bis Sie müssen ... aber wie das XML - Dokument erklärt , eine Codierung, das ist etwas , müssen Sie überlegen.
Jon Skeet
2
@Garry, das einfachste, was ich mir derzeit vorstellen kann, ist das zweite Beispiel in meiner Antwort. Wenn Sie XmlWriterdies jedoch mit der Factory-Methode erstellen , die ein XmlWriterSettingsObjekt übernimmt , und die OmitXmlDeclarationEigenschaft auf festgelegt ist true.
Jon Hanna
4
+1 Ihre Utf8StringWriterLösung ist sehr schön und sauber
Adriano Carneiro
17

Sehr gute Antwort mit Vererbung, denken Sie daran, den Initialisierer zu überschreiben

public class Utf8StringWriter : StringWriter
{
    public Utf8StringWriter(StringBuilder sb) : base (sb)
    {
    }
    public override Encoding Encoding { get { return Encoding.UTF8; } }
}
Sebastian Castaldi
quelle
danke, ich finde das die eleganteste Option
Prokurors
5

Ich habe diesen Blog-Beitrag gefunden, der das Problem sehr gut erklärt und einige verschiedene Lösungen definiert:

(toter Link entfernt)

Ich habe mich mit der Idee zufrieden gegeben, dass der beste Weg, dies zu tun, darin besteht, die XML-Deklaration im Speicher vollständig wegzulassen. Es tatsächlich ist UTF-16 an diesem Punkt sowieso, aber die XML - Deklaration scheint nicht sinnvoll , bis sie mit einer bestimmten Codierung in eine Datei geschrieben wurden; und selbst dann ist die Erklärung nicht erforderlich. Zumindest scheint es die Deserialisierung nicht zu brechen.

Wie @Jon Hanna erwähnt, kann dies mit einem XmlWriter erfolgen, der wie folgt erstellt wurde:

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