Сериализация объекта в формате UTF-8 XML в .NET - PullRequest
98 голосов
/ 05 октября 2010

Правильная утилизация объектов удалена для краткости, но я в шоке, если это самый простой способ кодировать объект как UTF-8 в памяти.Должен быть более легкий путь, не так ли?

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

Ответы [ 4 ]

249 голосов
/ 05 октября 2010

Нет, вы можете использовать StringWriter, чтобы избавиться от промежуточного звена MemoryStream. Однако, чтобы преобразовать его в XML, вам нужно использовать StringWriter, который переопределяет свойство Encoding:

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

Или, если вы еще не используете C # 6:

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

Тогда:

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

Очевидно, вы можете превратить Utf8StringWriter в более общий класс, который принимает любую кодировку в своем конструкторе - но, по моему опыту, UTF-8 является наиболее часто требуемой "пользовательской" кодировкой для StringWriter:)

Теперь, как говорит Джон Ханна, это все еще будет UTF-16 внутри, но, вероятно, в какой-то момент вы собираетесь передать его чему-то другому, чтобы преобразовать в двоичные данные ... на , что отметьте, что вы можете использовать приведенную выше строку, преобразовать ее в байты UTF-8, и все будет хорошо - потому что в декларации XML в качестве кодировки будет указано "utf-8".

РЕДАКТИРОВАТЬ: короткий, но полный пример, чтобы показать эту работу:

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

Результат:

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

Обратите внимание на объявленную кодировку "utf-8", которая, как я думаю, нам нужна.

51 голосов
/ 05 октября 2010

Ваш код не помещает UTF-8 в память, когда вы снова читаете его обратно в строку, поэтому он больше не в UTF-8, а обратно в UTF-16 (хотя в идеале лучше всего рассматривать строки вболее высокий уровень, чем любая кодировка, кроме случаев, когда это необходимо).

Чтобы получить действительные октеты UTF-8, вы можете использовать:

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

Я оставил то же самое, что и выушел.Я слегка предпочитаю следующее (с нормальным избавлением от него):

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

Это примерно такая же сложность, но показывает, что на каждом этапе есть разумный выбор сделать что-то еще, наиболеенажатие на них означает сериализацию в другое место, кроме памяти, например, в файл, поток TCP / IP, базу данных и т. д. В общем, это не так уж и многословно.

16 голосов
/ 30 января 2013

Очень хороший ответ с использованием наследования, просто не забудьте переопределить инициализатор

public class Utf8StringWriter : StringWriter
{
    public Utf8StringWriter(StringBuilder sb) : base (sb)
    {
    }
    public override Encoding Encoding { get { return Encoding.UTF8; } }
}
5 голосов
/ 21 января 2011

Я нашел этот пост в блоге, который очень хорошо объясняет проблему и определяет несколько различных решений:

(мертвая ссылка удалена)

Я согласился с идеей, что лучший способ сделать это - полностью опустить декларацию XML в памяти. На самом деле это - это UTF-16 на тот момент, но декларация XML не имеет смысла, пока не будет записана в файл с определенной кодировкой; и даже тогда декларация не требуется. По крайней мере, это не нарушает десериализацию.

Как упоминает @Jon Hanna, это можно сделать с помощью XmlWriter, созданного так:

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