Как десериализовать дату и время, допускающие значение NULL, из Rails-XML в C# Строка '' не является допустимым значением AllXsd.

Я получаю XML из API Rails, но при попытке его десериализации я продолжаю получать сообщение об ошибке «Строка '' не является допустимым значением AllXsd».

Я чувствую, что это должно быть очень просто и тривиально, и что мне просто не хватает какого-то или другого атрибута, связанного с XML, но я ничего не нахожу при поиске в Google. Все примеры, похоже, используют строку для значения Date-Time, а затем выполняют внутренний анализ, чтобы получить фактическое значение DateTime, допускающее значение NULL. Это сработает, но выглядит беспорядочно и, вероятно, будет выглядеть странно, когда кто-то увидит его в следующий раз, и в этот момент они, вероятно, попытаются заменить его простым свойством и либо пропустят значение 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; }
}

person feihtthief    schedule 04.08.2016    source источник


Ответы (1)


Проблема в том, что nil (в отличие от xsi:nil) не имеет особого значения, это просто еще один атрибут. Тот факт, что ваш XML использует его для обозначения одного и того же, не представляет интереса для сериализатора.

Как я понимаю, у вас есть два варианта:

  1. Выполняйте сериализацию в строку и обратно и самостоятельно обрабатывайте синтаксический анализ и преобразование в строку.
  2. Предварительно обработайте XML, чтобы заменить все экземпляры nil на xsi:nil.

Первый вариант довольно понятен (и вы упомянули об этом в своем вопросе). Второе можно сделать с помощью такого кода:

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(), сериализатору или получить новую строку XML, вызвав doc.ToString().

person Charles Mager    schedule 04.08.2016