Serializzazione di un oggetto come XML UTF-8 in .NET


112

Eliminazione corretta degli oggetti rimossa per brevità, ma sono scioccato se questo è il modo più semplice per codificare un oggetto come UTF-8 in memoria. Deve esserci un modo più semplice, no?

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


1
Sono confuso ... non è la codifica predefinita UTF-8?
flq

@flq, sì, l'impostazione predefinita è UTF-8, anche se non ha molta importanza dal momento che lo sta leggendo di nuovo in una stringa, così utf8EncodedXmlè UTF-16.
Jon Hanna

1
@ Garry, puoi chiarire, dato che Jon Skeet e io stiamo rispondendo a domande diverse. Vuoi serializzare l'oggetto come UTF-8 o vuoi una stringa XML che si dichiari come UTF-8 e quindi avrà la dichiarazione corretta quando verrà successivamente codificata in UTF-8? (nel qual caso il modo più semplice è non avere dichiarazioni, poiché è valido sia per UTF-8 che per UTF-16).
Jon Hanna

@ Jon Rileggendo, c'è ambiguità nella mia domanda. L'ho inviato a una stringa principalmente per scopi di debug. In pratica probabilmente trasmetterei byte, su disco o su HTTP, il che rende la tua risposta più direttamente pertinente al mio problema. Il problema principale che ho avuto è stata la dichiarazione di UTF-8 nell'XML, ma per essere più accurato dovrei evitare l'intermediario di una stringa in modo da inviare / persistere byte UTF-8 effettivi piuttosto che dipendenti dalla piattaforma (credo) codificante.
Garry Shutler

Risposte:


55

Il tuo codice non ottiene l'UTF-8 in memoria mentre lo leggi di nuovo in una stringa, quindi non è più in UTF-8, ma di nuovo in UTF-16 (anche se idealmente è meglio considerare le stringhe a un livello più alto di qualsiasi codifica, tranne quando forzato a farlo).

Per ottenere gli ottetti UTF-8 effettivi potresti usare:

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

Ho tralasciato lo stesso smaltimento che hai lasciato. Preferisco leggermente quanto segue (con lo smaltimento normale lasciato in):

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

Che è più o meno la stessa quantità di complessità, ma mostra che in ogni fase c'è una scelta ragionevole per fare qualcos'altro, la più urgente delle quali è serializzare in un luogo diverso dalla memoria, come un file, TCP / IP stream, database, ecc. Tutto sommato, non è così prolisso.


4
Anche. Se vuoi sopprimere la BOM puoi usare XmlWriter.Create(memoryStream, new XmlWriterSettings { Encoding = new UTF8Encoding(false) }).
ony

Se qualcuno (come me) ha bisogno di leggere l'XML creato come mostra Jon, ricordati di riposizionare il flusso di memoria su 0, altrimenti otterrai un'eccezione che dice "Manca l'elemento radice". Quindi fai questo: memStm.Position = 0; XmlReader xmlReader = XmlReader.Create (memStm)
Sudhanshu Mishra

276

No, puoi usare a StringWriterper sbarazzarti dell'intermedio MemoryStream. Tuttavia, per forzarlo in XML è necessario utilizzare un StringWriterche sovrascrive la Encodingproprietà:

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

O se non stai ancora usando C # 6:

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

Poi:

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

Ovviamente puoi trasformare Utf8StringWriterin una classe più generale che accetta qualsiasi codifica nel suo costruttore - ma nella mia esperienza UTF-8 è di gran lunga la codifica "personalizzata" più comunemente richiesta per un StringWriter:)

Ora, come dice Jon Hanna, questo sarà ancora UTF-16 internamente, ma presumibilmente lo passerai a qualcos'altro ad un certo punto, per convertirlo in dati binari ... a quel punto puoi usare la stringa sopra, convertitelo in byte UTF-8 e tutto andrà bene, perché la dichiarazione XML specificherà "utf-8" come codifica.

EDIT: un esempio breve ma completo per mostrare questo funzionamento:

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

Risultato:

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

Notare la codifica dichiarata di "utf-8" che è quello che volevamo, credo.


2
Anche quando sovrascrivi il parametro Encoding su StringWriter, invia comunque i dati scritti a StringBuilder, quindi è ancora UTF-16. E la stringa può essere sempre e solo UTF-16.
Jon Hanna

3
@ Jon: l'hai provato? Sì, e funziona. È la codifica dichiarata che è importante qui; ovviamente internamente la stringa è ancora UTF-16, ma ciò non fa alcuna differenza fino a quando non viene convertita in binario (che potrebbe utilizzare qualsiasi codifica, incluso UTF-8). La TextWriter.Encodingproprietà viene utilizzata dal serializzatore XML per determinare quale nome di codifica specificare all'interno del documento stesso.
Jon Skeet

2
@ Jon: E qual era la codifica dichiarata? Nella mia esperienza, questo è ciò che domande come questa stanno davvero cercando di fare: creare un documento XML che si dichiari in UTF-8. Come dici tu, è meglio non considerare il testo come in una codifica fino a quando non è necessario ... ma poiché il documento XML dichiara una codifica, è qualcosa che devi considerare.
Jon Skeet

2
@ Garry, il più semplice a cui riesco a pensare in questo momento è prendere il secondo esempio nella mia risposta, ma quando lo crei XmlWriterfallo con il metodo factory che accetta un XmlWriterSettingsoggetto e la OmitXmlDeclarationproprietà è impostata su true.
Jon Hanna

4
+1 La tua Utf8StringWritersoluzione è estremamente bella e pulita
Adriano Carneiro

17

Ottima risposta usando l'ereditarietà, ricorda solo di sovrascrivere l'inizializzatore

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

grazie, trovo che questa sia la più elegante delle opzioni
Prokurors

5

Ho trovato questo post del blog che spiega molto bene il problema e definisce alcune soluzioni diverse:

(collegamento morto rimosso)

Ho deciso che il modo migliore per farlo è omettere completamente la dichiarazione XML quando è in memoria. In realtà è UTF-16 a quel punto comunque, ma la dichiarazione XML non sembra significativa finché non è stata scritta in un file con una particolare codifica; e anche allora la dichiarazione non è richiesta. Almeno non sembra rompere la deserializzazione.

Come menzionato da @Jon Hanna, questo può essere fatto con un XmlWriter creato in questo modo:

XmlWriter writer = XmlWriter.Create (output, new XmlWriterSettings() { OmitXmlDeclaration = true });
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.