Sắp xếp thứ tự một đối tượng dưới dạng UTF-8 XML trong .NET


112

Loại bỏ việc xử lý đối tượng thích hợp cho ngắn gọn nhưng tôi bị sốc nếu đây là cách đơn giản nhất để mã hóa một đối tượng dưới dạng UTF-8 trong bộ nhớ. Phải có một cách dễ dàng hơn phải không?

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
Tôi bối rối ... không phải là mã hóa mặc định UTF-8?
flq

@flq, vâng, mặc định là UTF-8, mặc dù nó không quan trọng lắm vì anh ấy đang đọc lại thành chuỗi một lần nữa, utf8EncodedXmlUTF-16 cũng vậy.
Jon Hanna

1
@Garry, bạn có thể làm rõ không, vì Jon Skeet và tôi đang trả lời các câu hỏi khác nhau. Bạn muốn đối tượng được tuần tự hóa dưới dạng UTF-8 hay bạn muốn một chuỗi XML tự khai báo là UTF-8 và do đó sẽ có khai báo chính xác khi được mã hóa sau này bằng UTF-8? (trong trường hợp đó, cách đơn giản nhất là không cần khai báo, vì điều đó hợp lệ cho cả UTF-8 và UTF-16).
Jon Hanna

@Jon Đọc lại, có sự mơ hồ trong câu hỏi của tôi. Tôi đã có nó xuất ra một chuỗi chủ yếu cho mục đích gỡ lỗi. Trong thực tế, tôi có thể sẽ truyền trực tuyến các byte, sang đĩa hoặc qua HTTP, điều này làm cho câu trả lời của bạn liên quan trực tiếp hơn đến vấn đề của tôi. Vấn đề chính mà tôi gặp phải là khai báo UTF-8 trong XML, nhưng để chính xác hơn, tôi nên tránh trung gian của một chuỗi để tôi thực sự gửi / tồn tại các byte UTF-8 thay vì phụ thuộc vào nền tảng (tôi nghĩ vậy) mã hóa.
Garry Shutler

Câu trả lời:


55

Mã của bạn không đưa UTF-8 vào bộ nhớ khi bạn đọc lại thành chuỗi, do đó, mã của nó không còn ở UTF-8 nữa mà trở lại UTF-16 (mặc dù lý tưởng nhất là nên xem xét các chuỗi ở cấp cao hơn bất kỳ mã hóa nào, ngoại trừ trường hợp buộc phải làm như vậy).

Để có được các octet UTF-8 thực tế, bạn có thể sử dụng:

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

Tôi đã bỏ đi những thứ mà bạn đã bỏ đi. Tôi hơi ủng hộ những điều sau (với việc thải bỏ bình thường vẫn ở trong):

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

Đó là mức độ phức tạp như nhau, nhưng cho thấy rằng ở mọi giai đoạn đều có sự lựa chọn hợp lý để làm việc khác, điều cấp bách nhất trong số đó là tuần tự hóa đến một nơi nào đó khác ngoài bộ nhớ, chẳng hạn như một tệp, TCP / IP luồng, cơ sở dữ liệu, v.v. Nói chung, nó không thực sự dài dòng như vậy.


4
Cũng thế. Nếu bạn muốn ngăn chặn BOM, bạn có thể sử dụng XmlWriter.Create(memoryStream, new XmlWriterSettings { Encoding = new UTF8Encoding(false) }).
ony

Nếu ai đó (như tôi) cần đọc XML được tạo như Jon hiển thị, hãy nhớ đặt lại vị trí luồng bộ nhớ thành 0, nếu không bạn sẽ nhận được một ngoại lệ nói rằng "Phần tử gốc bị thiếu". Vì vậy, hãy làm điều này: memStm.Position = 0; XmlReader xmlReader = XmlReader.Create (memStm)
Sudhanshu Mishra

276

Không, bạn có thể sử dụng a StringWriterđể loại bỏ trung gian MemoryStream. Tuy nhiên, để buộc nó thành XML, bạn cần sử dụng một mã StringWriterghi đè thuộc Encodingtính:

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

Hoặc nếu bạn chưa sử dụng C # 6:

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

Sau đó:

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

Rõ ràng là bạn có thể tạo Utf8StringWriterthành một lớp tổng quát hơn chấp nhận bất kỳ mã hóa nào trong hàm tạo của nó - nhưng theo kinh nghiệm của tôi UTF-8 cho đến nay là mã hóa "tùy chỉnh" được yêu cầu phổ biến nhất cho StringWriter:)

Bây giờ là Jon Hanna nói, đây vẫn sẽ là UTF-16 trong nội bộ, nhưng có lẽ bạn sẽ vượt qua nó để cái gì khác tại một số điểm, để chuyển đổi nó thành dữ liệu nhị phân ... tại đó điểm bạn có thể sử dụng chuỗi trên, chuyển đổi nó thành UTF-8 byte và tất cả sẽ ổn - vì khai báo XML sẽ chỉ định "utf-8" làm mã hóa.

CHỈNH SỬA: Một ví dụ ngắn gọn nhưng đầy đủ để cho thấy điều này đang hoạt động:

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

Kết quả:

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

Lưu ý rằng mã hóa được khai báo của "utf-8" là những gì chúng tôi muốn, tôi tin.


2
Ngay cả khi bạn ghi đè tham số Mã hóa trên StringWriter, nó vẫn gửi dữ liệu đã viết tới StringBuilder, vì vậy nó vẫn là UTF-16. Và chuỗi chỉ có thể là UTF-16.
Jon Hanna

3
@Jon: Bạn đã thử chưa? Tôi có, và nó hoạt động. Đây là mã hóa được khai báo quan trọng ở đây; rõ ràng bên trong chuỗi vẫn là UTF-16, nhưng điều đó không tạo ra bất kỳ sự khác biệt nào cho đến khi nó được chuyển đổi thành nhị phân (có thể sử dụng bất kỳ mã hóa nào, bao gồm UTF-8). Các TextWriter.Encodingtài sản được sử dụng bởi các serializer XML để xác định tên mã hóa để xác định trong tài liệu riêng của mình.
Jon Skeet

2
@Jon: Và mã hóa đã khai báo là gì? Theo kinh nghiệm của tôi, đó là những gì mà những câu hỏi như thế này thực sự đang cố gắng thực hiện - tạo một tài liệu XML tự khai báo là ở UTF-8. Như bạn nói, tốt nhất là không nên coi văn bản là trong bất kỳ bảng mã nào cho đến khi bạn cần ... nhưng vì tài liệu XML khai báo một bảng mã, đó là điều bạn cần xem xét.
Jon Skeet

2
@Garry, đơn giản nhất mà tôi có thể nghĩ đến ngay bây giờ là lấy ví dụ thứ hai trong câu trả lời của tôi, nhưng khi bạn tạo XmlWriterđiều này với phương thức factory lấy một XmlWriterSettingsđối tượng và đặt thuộc OmitXmlDeclarationtính true.
Jon Hanna

4
+1 của bạn Utf8StringWritergiải pháp là vô cùng thoải mái và sạch sẽ
Adriano Carneiro

17

Câu trả lời rất hay khi sử dụng kế thừa, chỉ cần nhớ ghi đè bộ khởi tạo

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

cảm ơn, tôi thấy đây là lựa chọn thanh lịch nhất
Prokurors

5

Tôi đã tìm thấy bài đăng trên blog này giải thích vấn đề rất tốt và xác định một số giải pháp khác nhau:

(đã xóa liên kết chết)

Tôi đã giải quyết cho ý tưởng rằng cách tốt nhất để làm điều đó là loại bỏ hoàn toàn khai báo XML khi ở trong bộ nhớ. Nó thực sự UTF-16 vào thời điểm đó, nhưng khai báo XML dường như không có ý nghĩa cho đến khi nó được ghi vào một tệp có mã hóa cụ thể; và thậm chí sau đó không cần khai báo. Nó dường như không phá vỡ quá trình deserialization, ít nhất.

Như @Jon Hanna đã đề cập, điều này có thể được thực hiện với một XmlWriter được tạo như thế này:

XmlWriter writer = XmlWriter.Create (output, new XmlWriterSettings() { OmitXmlDeclaration = true });
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.