การทำให้วัตถุเป็นอนุกรมเป็น 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 แม้ว่ามันจะไม่สำคัญมากนักเนื่องจากเขาอ่านมันกลับมาเป็นสตริงอีกครั้งดังนั้นutf8EncodedXmlUTF-16
Jon Hanna

1
@Garry คุณช่วยชี้แจงได้ไหมเนื่องจาก Jon Skeet และฉันกำลังตอบคำถามที่แตกต่างกัน คุณต้องการให้อ็อบเจกต์ต่อเนื่องเป็น UTF-8 หรือคุณต้องการสตริง XML ที่ประกาศตัวเองเป็น UTF-8 และด้วยเหตุนี้จะมีการประกาศที่ถูกต้องเมื่อเข้ารหัสใน UTF-8 ในภายหลัง? (ซึ่งในกรณีนี้วิธีที่ง่ายที่สุดคือไม่มีการประกาศเนื่องจากใช้ได้กับทั้ง UTF-8 และ UTF-16)
Jon Hanna

@ จอนอ่านย้อนหลังมีความคลุมเครือในคำถามของฉัน ฉันให้เอาต์พุตเป็นสตริงส่วนใหญ่เพื่อวัตถุประสงค์ในการดีบัก ในทางปฏิบัติฉันน่าจะสตรีมไบต์ไม่ว่าจะเป็นดิสก์หรือผ่าน HTTP ซึ่งทำให้คำตอบของคุณเกี่ยวข้องโดยตรงกับปัญหาของฉันมากขึ้น ปัญหาหลักที่ฉันมีคือการประกาศ UTF-8 ใน XML แต่เพื่อให้ถูกต้องมากขึ้นฉันควรหลีกเลี่ยงตัวกลางของสตริงเพื่อที่ฉันจะส่ง / คงอยู่ UTF-8 ไบต์แทนที่จะขึ้นอยู่กับแพลตฟอร์ม (ฉันคิดว่า) การเข้ารหัส
Garry Shutler

คำตอบ:


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 สตรีมฐานข้อมูล ฯลฯ สรุปแล้วมันไม่ได้เป็นแบบ verbose


4
ด้วย หากคุณต้องการระงับ BOM คุณสามารถใช้XmlWriter.Create(memoryStream, new XmlWriterSettings { Encoding = new UTF8Encoding(false) }).
ony

หากมีคน (เช่นฉัน) ต้องการอ่าน XML ที่สร้างขึ้นเหมือนที่จอนแสดงอย่าลืมเปลี่ยนตำแหน่งสตรีมหน่วยความจำเป็น 0 มิฉะนั้นคุณจะได้รับข้อยกเว้นว่า "องค์ประกอบรูทขาดหายไป" ให้ทำสิ่งนี้: memStm.Position = 0; XmlReader xmlReader = XmlReader.Create (memStm)
Sudhanshu Mishra

277

ไม่ได้คุณสามารถใช้ 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" เป็นการเข้ารหัส

แก้ไข: ตัวอย่างสั้น ๆ แต่สมบูรณ์เพื่อแสดงการทำงานนี้:

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 เท่านั้น
Jon Hanna

4
@ จอน: ลองรึยัง? ฉันมีและได้ผล เป็นการเข้ารหัสที่ประกาศซึ่งมีความสำคัญที่นี่ เห็นได้ชัดว่าสตริงภายในยังคงเป็น UTF-16 แต่ก็ไม่ได้สร้างความแตกต่างใด ๆ จนกว่าจะถูกแปลงเป็นไบนารี (ซึ่งอาจใช้การเข้ารหัสใด ๆ รวมถึง UTF-8) TextWriter.Encodingคุณสมบัติถูกใช้โดย serializer XML เพื่อตรวจสอบว่าชื่อการเข้ารหัสเพื่อระบุในเอกสารของตัวเอง
Jon Skeet

2
@ จอน: แล้วการเข้ารหัสที่ประกาศคืออะไร? จากประสบการณ์ของผมว่าเป็นสิ่งที่คำถามเช่นนี้จริงๆพยายามที่จะทำ - สร้างเอกสาร XML ซึ่งประกาศตัวเองให้เป็น UTF-8 อย่างที่คุณพูดคุณไม่ควรพิจารณาว่าข้อความนั้นอยู่ในการเข้ารหัสใด ๆจนกว่าคุณจะต้อง ... แต่เนื่องจากเอกสาร XML ประกาศการเข้ารหัสจึงเป็นสิ่งที่คุณต้องพิจารณา
Jon Skeet

2
@Garry, ที่ง่ายที่สุดที่ฉันสามารถคิดตอนนี้คือการใช้ตัวอย่างที่สองในคำตอบของฉัน แต่เมื่อคุณสร้างXmlWriterทำได้ด้วยวิธีการโรงงานที่ใช้เวลาXmlWriterSettingsวัตถุและมีการตั้งค่าคุณสมบัติการOmitXmlDeclaration true
Jon Hanna

4
+1 Utf8StringWriterโซลูชันของคุณดีและสะอาดมาก
Adriano Carneiro

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 ดูเหมือนจะไม่มีความหมายจนกว่าจะถูกเขียนไปยังไฟล์ที่มีการเข้ารหัสเฉพาะ และไม่จำเป็นต้องมีการประกาศ ดูเหมือนจะไม่ทำลาย deserialization อย่างน้อย

ดังที่ @Jon Hanna กล่าวถึงสิ่งนี้สามารถทำได้ด้วย XmlWriter ที่สร้างขึ้นเช่นนี้:

XmlWriter writer = XmlWriter.Create (output, new XmlWriterSettings() { OmitXmlDeclaration = true });
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.