在.NET中将对象序列化为UTF-8 XML


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吗?
2010年

@flq,是的,默认值是UTF-8,尽管没关系,因为他再次将其读回到字符串中,所以它utf8EncodedXml也是UTF-16。
乔恩·汉娜

1
@加里,你能澄清一下,因为乔恩·斯凯特和我正在回答不同的问题。您是否要将该对象序列化为UTF-8,还是想要一个将其自身声明为UTF-8的XML字符串,从而在以后以UTF-8进行编码时具有正确的声明?(在这种情况下,最简单的方法是不声明,因为这对UTF-8和UTF-16均有效)。
乔恩·汉娜

@Jon回读,我的问题含糊不清。我将其输出为字符串主要是为了调试。实际上,我可能会将字节流传输到磁盘或HTTP上,这使您的答案与我的问题更直接相关。我遇到的主要问题是在XML中声明UTF-8,但更准确地说,我应避免使用字符串的中介,这样我才可以实际发送/保留UTF-8字节,而不是依赖于平台(我认为)编码。
加里·舒特勒

Answers:


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) })
2012年

如果有人(像我一样)需要读取像Jon节目所示创建的XML,请记住将内存流重新定位为0,否则您将收到一个异常消息,提示“缺少根元素”。这样做:memStm.Position = 0; XmlReader xmlReader = XmlReader.Create(memStm)
Sudhanshu Mishra 2015年

276

不,您可以使用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:)

现在,就像乔恩·汉纳(Jon Hanna)所说的那样,内部仍将是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
即使您在StringWriter上覆盖Encoding参数,它仍然会将写入的数据发送到StringBuilder,因此它仍然是UTF-16。而且该字符串只能是UTF-16。
乔恩·汉纳

3
@Jon:你尝试过吗?我有,它有效。在这里重要的是声明的编码。很明显,内部字符串仍然是UTF-16,但这在转换为二进制(可以使用任何编码,包括UTF-8)之前没有任何区别。TextWriter.EncodingXML序列化程序使用该属性来确定要在文档本身中指定的编码名称。
乔恩·斯基特

2
@Jon:声明的编码是什么?以我的经验,这就是此类问题的真正尝试-创建一个声明自己为UTF-8的XML文档。就像您说的那样,除非需要,否则最好不要将文本视为任何编码。但是,由于XML文档声明了一种编码,因此您需要考虑这一点。
乔恩·斯基特

2
@Garry,最简单的方法是,我现在想的是在答案中举第二个例子,但是当您使用XmlWriter带有一个XmlWriterSettings对象并将OmitXmlDeclaration属性设置为的factory方法创建该例子时,就可以这样做true
乔恩·汉纳

4
+1您的Utf8StringWriter解决方案非常好而且干净
Adriano Carneiro


5

我发现这篇博客文章很好地解释了这个问题,并定义了一些不同的解决方案:

(死链接已删除)

我已经同意,最好的方法是在内存中完全省略XML声明。那时实际上它实际上 UTF-16,但是XML声明直到以特定编码写入文件才显得没有意义。即使这样,也不需要声明。至少它似乎并没有破坏反序列化。

正如@Jon Hanna提到的那样,可以使用这样创建的XmlWriter来完成此操作:

XmlWriter writer = XmlWriter.Create (output, new XmlWriterSettings() { OmitXmlDeclaration = true });
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.