Серіалізація об'єкта як UTF-8 XML в .NET


112

Правильне видалення об'єктів видалено для стислості, але я вражений, якщо це найпростіший спосіб кодування об'єкта як 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();


1
Я розгублений ... чи не кодування UTF-8 за замовчуванням?
flq

@flq, так, за замовчуванням є UTF-8, хоча це не має великого значення, оскільки він знову читає його назад у рядок, utf8EncodedXmlяк і UTF-16.
Джон Ханна

1
@Garry, ви можете уточнити, оскільки ми з Джоном Скітом відповідаємо на різні запитання. Ви хочете, щоб об'єкт був серіалізований як UTF-8, чи ви хочете, щоб рядок XML, який оголошує себе як UTF-8, і, отже, матиме правильну заяву, коли пізніше закодується в UTF-8? (у такому випадку найпростішим способом є відсутність декларації, оскільки це справедливо як для UTF-8, так і для UTF-16).
Джон Ханна

@Jon Reading назад, в моєму питанні є неоднозначність. У мене він виводив рядок здебільшого для налагодження. На практиці я, швидше за все, передаватимуть байти або на диск, або через HTTP, що робить вашу відповідь більш безпосередньою для моєї проблеми. Головною проблемою у мене було декларування UTF-8 у XML, але щоб бути точнішим, я повинен уникати посередника рядка, щоб я фактично передавав / зберігав байти UTF-8, а не залежав від платформи (я думаю) кодування.
Гаррі Шутлер

Відповіді:


55

Ваш код не потрапляє в пам'ять 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 потік, база даних тощо. Загалом, це не дуже багатослівний текст.


4
Також. Якщо ви хочете придушити BOM, ви можете використовувати XmlWriter.Create(memoryStream, new XmlWriterSettings { Encoding = new UTF8Encoding(false) }).
они

Якщо комусь (як я) потрібно прочитати XML, створений як Jon show, не забудьте змінити потік пам'яті на 0, інакше ви отримаєте виняток із повідомленням "Root element is missing". Тому зробіть це: memStm.Position = 0; XmlReader xmlReader = XmlReader.Create (memStm)
Sudhanshu Mishra

276

Ні, ви можете використовувати a, 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" як кодування.

EDIT: Короткий, але повний приклад, щоб показати це:

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", яке ми хотіли, я вважаю.


2
Навіть коли ви переосмислюєте параметр Encoding на StringWriter, він все одно надсилає письмові дані до StringBuilder, тому це все ще UTF-16. І рядок може бути лише UTF-16.
Джон Ханна

3
@Jon: Ви пробували? У мене є, і це працює. Тут важливо заявлене кодування; Очевидно, що всередині рядка все ще є UTF-16, але це не має ніякого значення, поки він не буде перетворений у бінарний (який може використовувати будь-яке кодування, включаючи UTF-8). TextWriter.EncodingВластивість використовується XML серіалізатор , щоб визначити , яка кодування ім'я вказувати в самому документі.
Джон Скіт

2
@Jon: А що було задекларовано кодування? На мій досвід, ось що насправді намагаються зробити такі питання - створити XML-документ, який оголошує себе в UTF-8. Як ви кажете, найкраще не вважати текст будь-яким кодуванням, поки вам не потрібно ..., але як XML-документ оголошує кодування, це те, що вам потрібно врахувати.
Джон Скіт

2
@Garry, найпростіший, про який я зараз можу подумати, - це взяти другий приклад у своїй відповіді, але коли ви створюєте XmlWriterце за допомогою заводського методу, який бере XmlWriterSettingsоб’єкт, і маєте OmitXmlDeclarationвластивість true.
Джон Ханна

4
+1 Ваше Utf8StringWriterрішення надзвичайно приємне та чисте
Адріано Карнейро

17

Дуже хороша відповідь, використовуючи успадкування, просто пам’ятайте, що перекриє ініціалізатор

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

дякую, я вважаю це найелегантнішим варіантом
Prokurors

5

Я знайшов цю публікацію в блозі, яка дуже добре пояснює проблему, і визначає кілька різних рішень:

(мертве посилання видалено)

Я погодився з думкою, що найкращий спосіб зробити це - повністю опустити декларацію XML, коли вона знаходиться в пам'яті. Насправді це UTF-16 у будь-якому разі, але декларація XML не здається сенсовою, поки вона не буде записана у файл із певним кодуванням; і навіть тоді декларація не потрібна. Схоже, це не порушує десяріалізацію, принаймні.

Як згадує @Jon Hanna, це можна зробити за допомогою XmlWriter, створеного так:

XmlWriter writer = XmlWriter.Create (output, new XmlWriterSettings() { OmitXmlDeclaration = true });
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.