当BOM存在时,反序列化UTF-8编码的XML失败

时间:2017-02-01 19:31:46

标签: c# xml utf-8 deserialization

我有一个非常奇怪的问题:我正在构建一个第三方系统的接口,该系统通过SFTP服务器提供XML文件(使用UTF-8编码)。

我在C#代码中下载这些文件,然后尝试将它们反序列化为C#对象。对于大多数文件来说,这非常有效,但对于某些人来说,它只是一直在轰炸....

想象一下像这样的DTO课程:

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
}

和这样的XML:

<?xml version="1.0" encoding="utf-8"?>
<Person>
    <FirstName>John</FirstName>
    <LastName>Doe</LastName>
    <Age>42</Age>
</Person>

我在C#代码中所做的就是:

  • 从SFTP服务器下载文件内容作为字节数组
  • 从该二进制数据中提取UTF-8编码的字符串
  • 将该字符串表示用于反序列化过程

这样的事情:

// get bytes from SFTP server
byte[] content = _sftpClient.Download(fileName);

// convert content to a UTF-8 string
string contentAsString = Encoding.UTF8.GetString(content);

try
{
    // deserialize that string into a "Person" instance
    XmlReaderSettings settings = new XmlReaderSettings();
    settings.IgnoreComments = true;
    settings.IgnoreProcessingInstructions = true;
    settings.IgnoreWhitespace = true;
    settings.CheckCharacters = false;

    using (StringReader str = new StringReader(contentAsString))
    using (XmlReader xr = XmlReader.Create(str, settings))
    {
        XmlSerializer ser = new XmlSerializer(typeof(Person));

        if (ser.CanDeserialize(xr))
        {
            Person person = ser.Deserialize(xr) as Person;
        }
    }
}
catch (Exception exc)
{
    Console.WriteLine("ERROR: {0} - {1}", exc.GetType().Name, exc.Message);
}

现在我分析了有效的文件和那些没有的文件 - 区别在于二进制数据中的三字节前缀(0xEF 0xBB 0xBF) - “Unicode BOM”(字节顺序标记)

我知道BOM,这就是我没有直接使用从SFTP服务器获取的二进制数据的原因。当我将这些类型的文件转换为XML字符串contentAsString时,此字符串显示是相同的 - 至少我看不出任何差异。

但是开头(在二进制数据中)具有3字节BOM的文件导致反序列化在此行上失败

if (ser.CanDeserialize(xr))

有错误:

  

SystemException:根级别的数据无效。第1行,第1位。

字符串如何知道/“保留”有关3字节BOM的信息呢?我期望通过将字节数组转换为UTF-8编码字符串,任何差异都会消失,而BOM应该不再相关......

关于如何在没有 3字节BOM的情况下可靠地处理文件 的任何想法?

1 个答案:

答案 0 :(得分:2)

不是从byte []创建string并将其用作XmlReader的输入,而是使用MemoryStream

        // get bytes from SFTP server
        byte[] content = _sftpClient.Download(fileName);

        try
        {
            XmlReaderSettings settings = new XmlReaderSettings();
            settings.IgnoreComments = true;
            settings.IgnoreProcessingInstructions = true;
            settings.IgnoreWhitespace = true;
            settings.CheckCharacters = false;

            using(var memoryStream = new MemoryStream(content))
            using (XmlReader xr = XmlReader.Create(memoryStream, settings))
            {
                XmlSerializer ser = new XmlSerializer(typeof(Person));

                if (ser.CanDeserialize(xr))
                {
                    Person person = ser.Deserialize(xr) as Person;
                }
            }
        }
        catch (Exception exc)
        {
            Console.WriteLine("ERROR: {0} - {1}", exc.GetType().Name, exc.Message);
        }