如何从C#中的Rails-XML反序列化可空日期时间"字符串''不是有效的AllXsd值。"

时间:2016-08-04 10:45:44

标签: c# ruby-on-rails xml deserialization

我从Rails API获取XML,但在尝试反序列化时,我不断收到错误"字符串''不是有效的AllXsd值。"

我觉得这应该是非常容易和琐碎的,我只是缺少一些或其他XML相关的属性,但我在谷歌上找不到任何东西。所有示例似乎都使用字符串作为Date-Time值,然后自己进行内部解析以获得实际可为空的DateTime。这会起作用,但感觉很乱,下次有人看到它时可能看起来很奇怪,此时他们可能会尝试用一个简单的属性替换它,并且在测试过程中错过null或者卡在我现在的位置。

请原谅以下示例的详细内容,但我已尝试将其从实际实现中减少到最少量的相关代码。

class Program
{
    static void Main(string[] args)
    {
        var samples = new Dictionary<string, DateTime?>{
            { // from Rails with populated datetime value
                "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<response>\r\n  <latest-playing-at type=\"datetime\">2016-07-22T15:24:22+00:00</latest-playing-at>\r\n</response>\r\n"
                ,new DateTime(2016,07,22,17,24,22,DateTimeKind.Local)},
            { // from Rails with nil datetime value (THIS IS THE CASE I CARE ABOUT)
                "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<response>\r\n  <latest-playing-at nil=\"true\"/>\r\n</response>\r\n"
                ,(DateTime?)null},
            { // (Test) Serialized from dotnet with null value
                "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<response xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">\r\n  <latest-playing-at xsi:nil=\"true\" />\r\n</response>"
                    ,(DateTime?)null},
        };
        foreach (var sample in samples)
        {
            var xml = sample.Key;
            var expected = sample.Value;
            try
            {
                var testClass = DeserializeFromString<TestClass>(xml);
                Console.WriteLine("Expected / Actual : {0} / {1}", expected, testClass.LatestPlayingAt);
                Console.WriteLine(
                    DateTime.Equals(expected, testClass.LatestPlayingAt) ? "OK" : "Different" // ok to be different depening on time zones
                );
            }
            catch (Exception exc)
            {
                Console.WriteLine("Error: {0}", exc.Message);
                Console.WriteLine(exc);
            }
        }
        Console.ReadKey(true);
    }

    public static T DeserializeFromString<T>(string inputXML)
    {
        using (MemoryStream inStream = new MemoryStream(new UTF8Encoding(false).GetBytes(inputXML)))
        {
            T result = DeserializeFromStream<T>(inStream);
            return result;
        }
    }

    public static T DeserializeFromStream<T>(Stream inStream)
    {
        XmlSerializer serializer = new XmlSerializer(typeof(T));
        return (T)serializer.Deserialize(inStream);
    }
}

[XmlRoot("response")]
public class TestClass
{
    //[XmlElement("latest-playing-at")]                                                                                          // The string '' is not a valid AllXsd value.
    //[XmlElement("latest-playing-at", Type = typeof(DateTime?))]                                                                // The string '' is not a valid AllXsd value.
    //[XmlElement("latest-playing-at", Type = typeof(DateTime?), IsNullable = true)]                                             // The string '' is not a valid AllXsd value.
    //[XmlElement("latest-playing-at", Type = typeof(Nullable<DateTime>), IsNullable = true, Form = XmlSchemaForm.None)]         // The string '' is not a valid AllXsd value.
    [XmlElement("latest-playing-at", Type = typeof(Nullable<DateTime>), IsNullable = true, Form = XmlSchemaForm.Unqualified)]    // The string '' is not a valid AllXsd value.
    public DateTime? LatestPlayingAt { get; set; }
}

1 个答案:

答案 0 :(得分:1)

问题是nil(与xsi:nil相对)没有任何特殊含义,它只是另一个属性。您的XML碰巧使用它来表示相同的事情对序列化程序没有任何兴趣。

在我看来,你有两个选择:

  1. 串行进出字符串并自行处理解析和转换为字符串。
  2. 预处理XML以将nil的所有实例替换为xsi:nil
  3. 第一个选项是相当自我解释的(你在你的问题中提到它)。第二个可以用这样的代码完成:

    var doc = XDocument.Parse(xml);
    
    XNamespace xsi = "http://www.w3.org/2001/XMLSchema-instance";
    
    var nilAttributes = doc.Descendants()
        .Attributes("nil")
        .Where(x => x.Value == "true");
    
    foreach (var attribute in nilAttributes)
    {
        var element = attribute.Parent;
        attribute.Remove();            
        element.Add(new XAttribute(xsi + "nil", true));
    }
    

    然后,您可以将XmlReader创建的doc.CreateReader()传递给序列化程序,或通过调用doc.ToString()获取新的XML字符串。