简体   繁体   English

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

[英]How to deserialize a nullable datetime from Rails-XML in C# “The string '' is not a valid AllXsd value.”

I am getting back XML from a Rails API, but when attempting to deserialize it I keep getting the error "The string '' is not a valid AllXsd value." 我正在从Rails API中获取XML,但是在尝试对其进行反序列化时,我始终收到错误消息“字符串”不是有效的AllXsd值。”

I feel that this should be really easy and trivial and that I am just missing some or other XML related attribute, but I'm not finding anything when looking on google. 我觉得这应该真的很容易并且琐碎,而且我只是缺少一些或其他与XML相关的属性,但是在google上找不到任何东西。 All examples there seem to use a string for the Date-Time value and then do internal parsing themselves to get to the actual nullable DateTime. 那里的所有示例似乎都使用字符串作为Date-Time值,然后自行进行内部解析以获取实际的可为null的DateTime。 That would work but feels messy and would probably look strange the next time someone sees it, at which point they'd probably try to replace it with a simple property and either miss the null during testing or be stuck where I am now. 那行得通,但感觉很混乱,下次有人看到它时可能看起来很奇怪,这时他们可能会尝试用一个简单的属性替换它,或者在测试期间错过了null或卡在了我现在的位置。

Pardon the verbosity of the following example, but I've tried to reduce it to the smallest amount of related code from our actual implementation. 请原谅以下示例的冗长性,但我尝试将其减少到实际实现中的相关代码量最小。

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

The issue is nil (as opposed to xsi:nil ) doesn't have any special meaning, it's just another attribute. 问题是nil (与xsi:nil相反)没有任何特殊含义,它只是另一个属性。 The fact your XML happens to use it to mean the same thing isn't of any interest to the serialiser. 您的XML恰好使用它来表示相同的事实的事实对于序列化器来说没有任何意义。

As I see it, you have two options: 如我所见,您有两个选择:

  1. Serialise to and from a string and handle the parsing and conversion to string yourself. 与字符串串行化,并自己处理解析和转换为字符串。
  2. Pre-process the XML to replace all instances of nil with xsi:nil . 预处理XML,以使用xsi:nil替换所有nil实例。

The first option is fairly self explanatory (and you mentioned it in your question). 第一种选择是相当自我解释的(您在问题中提到了它)。 The second can be done with code like this: 第二个可以用这样的代码完成:

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

You can then pass the XmlReader created by doc.CreateReader() to the serialiser or get the new XML string by calling doc.ToString() . 然后,您可以将由doc.CreateReader()创建的XmlReader传递给序列化程序,或者通过调用doc.ToString()获得新的XML字符串。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM