diff --git a/RestSharp.Tests/XmlTests.cs b/RestSharp.Tests/XmlTests.cs index 8a3b15a2e..c5c942967 100644 --- a/RestSharp.Tests/XmlTests.cs +++ b/RestSharp.Tests/XmlTests.cs @@ -254,47 +254,47 @@ public void Can_Deserialize_Elements_to_Nullable_Values() Assert.Equal(new Guid(GuidString), output.UniqueId); } - [Fact] - public void Can_Deserialize_TimeSpan() - { - var culture = CultureInfo.InvariantCulture; - var doc = new XDocument(culture); - - TimeSpan? nullTimespan = null; - TimeSpan? nullValueTimeSpan = new TimeSpan(21, 30, 7); - - var root = new XElement("Person"); - root.Add(new XElement("Tick", new TimeSpan(468006))); - root.Add(new XElement("Millisecond", new TimeSpan(0, 0, 0, 0, 125))); - root.Add(new XElement("Second", new TimeSpan(0, 0, 8))); - root.Add(new XElement("Minute", new TimeSpan(0, 55, 2))); - root.Add(new XElement("Hour", new TimeSpan(21, 30, 7))); - root.Add(new XElement("NullableWithoutValue", nullTimespan)); - root.Add(new XElement("NullableWithValue", nullValueTimeSpan)); - - doc.Add(root); - - var xml = new XmlDeserializer - { - Culture = culture, - }; - - var response = new RestResponse { Content = doc.ToString() }; - - var d = new XmlDeserializer() - { - Culture = culture, - }; - var payload = d.Deserialize(response); - Assert.Equal(new TimeSpan(468006), payload.Tick); - Assert.Equal(new TimeSpan(0, 0, 0, 0, 125), payload.Millisecond); - Assert.Equal(new TimeSpan(0, 0, 8), payload.Second); - Assert.Equal(new TimeSpan(0, 55, 2), payload.Minute); - Assert.Equal(new TimeSpan(21, 30, 7), payload.Hour); - Assert.Null(payload.NullableWithoutValue); - Assert.NotNull(payload.NullableWithValue); - Assert.Equal(new TimeSpan(21, 30, 7), payload.NullableWithValue.Value); - } + [Fact] + public void Can_Deserialize_TimeSpan() + { + var culture = CultureInfo.InvariantCulture; + var doc = new XDocument(culture); + + TimeSpan? nullTimespan = null; + TimeSpan? nullValueTimeSpan = new TimeSpan(21, 30, 7); + + var root = new XElement("Person"); + root.Add(new XElement("Tick", new TimeSpan(468006))); + root.Add(new XElement("Millisecond", new TimeSpan(0, 0, 0, 0, 125))); + root.Add(new XElement("Second", new TimeSpan(0, 0, 8))); + root.Add(new XElement("Minute", new TimeSpan(0, 55, 2))); + root.Add(new XElement("Hour", new TimeSpan(21, 30, 7))); + root.Add(new XElement("NullableWithoutValue", nullTimespan)); + root.Add(new XElement("NullableWithValue", nullValueTimeSpan)); + + doc.Add(root); + + var xml = new XmlDeserializer + { + Culture = culture, + }; + + var response = new RestResponse { Content = doc.ToString() }; + + var d = new XmlDeserializer() + { + Culture = culture, + }; + var payload = d.Deserialize(response); + Assert.Equal(new TimeSpan(468006), payload.Tick); + Assert.Equal(new TimeSpan(0, 0, 0, 0, 125), payload.Millisecond); + Assert.Equal(new TimeSpan(0, 0, 8), payload.Second); + Assert.Equal(new TimeSpan(0, 55, 2), payload.Minute); + Assert.Equal(new TimeSpan(21, 30, 7), payload.Hour); + Assert.Null(payload.NullableWithoutValue); + Assert.NotNull(payload.NullableWithValue); + Assert.Equal(new TimeSpan(21, 30, 7), payload.NullableWithValue.Value); + } [Fact] public void Can_Deserialize_Custom_Formatted_Date() @@ -496,8 +496,8 @@ public void Can_Deserialize_Names_With_Underscores_Without_Matching_Case_On_Defa Assert.Equal (5, p.Foes.Count); Assert.Equal ("Yankees", p.Foes.Team); } - - [Fact] + + [Fact] public void Can_Deserialize_Lower_Cased_Root_Elements_With_Dashes() { var doc = CreateDashesXml(); @@ -640,6 +640,41 @@ public void Can_Deserialize_Mixture_Of_Empty_Elements_With_Attributes_And_Popula Assert.Null(output.Id); Assert.Null(output.StartDate); Assert.Equal(new Guid(GuidString), output.UniqueId); + } + + [Fact] + public void Can_Deserialize_DateTimeOffset() + { + var culture = CultureInfo.InvariantCulture; + var doc = new XDocument(culture); + + DateTimeOffset DateTimeOffset = new DateTimeOffset(2013, 02, 08, 9, 18, 22, TimeSpan.FromHours(10)); + DateTimeOffset? NullableDateTimeOffsetWithValue = new DateTimeOffset(2013, 02, 08, 9, 18, 23, TimeSpan.FromHours(10)); + + var root = new XElement("Dates"); + root.Add(new XElement("DateTimeOffset", DateTimeOffset)); + root.Add(new XElement("NullableDateTimeOffsetWithNull", string.Empty)); + root.Add(new XElement("NullableDateTimeOffsetWithValue", NullableDateTimeOffsetWithValue)); + + doc.Add(root); + + var xml = new XmlDeserializer + { + Culture = culture, + }; + + var response = new RestResponse { Content = doc.ToString() }; + + var d = new XmlDeserializer() + { + Culture = culture, + }; + var payload = d.Deserialize(response); + Assert.Equal(DateTimeOffset, payload.DateTimeOffset); + Assert.Null(payload.NullableDateTimeOffsetWithNull); + + Assert.True(payload.NullableDateTimeOffsetWithValue.HasValue); + Assert.Equal(NullableDateTimeOffsetWithValue, payload.NullableDateTimeOffsetWithValue); } private static string CreateUnderscoresXml() @@ -771,10 +806,10 @@ private static string CreateDashesXml() doc.Add(root); return doc.ToString(); } - - private static string CreateLowerCasedRootElementWithDashesXml() + + private static string CreateLowerCasedRootElementWithDashesXml() { - var doc = new XDocument(); + var doc = new XDocument(); var root = new XElement("incoming-invoices", new XElement("incoming-invoice", new XElement("concept-id", 45) @@ -934,5 +969,6 @@ private static string CreateXmlWithAttributesAndNullValuesAndPopulatedValues() return doc.ToString(); } + } } diff --git a/RestSharp/Deserializers/XmlAttributeDeserializer.cs b/RestSharp/Deserializers/XmlAttributeDeserializer.cs index 8d7ca4f8b..9b3f9bec6 100644 --- a/RestSharp/Deserializers/XmlAttributeDeserializer.cs +++ b/RestSharp/Deserializers/XmlAttributeDeserializer.cs @@ -55,7 +55,7 @@ public T Deserialize(IRestResponse response) RemoveNamespace(doc); } - var x = Activator.CreateInstance(); + var x = Activator.CreateInstance(); var objType = x.GetType(); if (objType.IsSubclassOfRawGeneric(typeof(List<>))) diff --git a/RestSharp/Deserializers/XmlDeserializer.cs b/RestSharp/Deserializers/XmlDeserializer.cs index 346290ce1..dff1c938b 100644 --- a/RestSharp/Deserializers/XmlDeserializer.cs +++ b/RestSharp/Deserializers/XmlDeserializer.cs @@ -22,7 +22,8 @@ using RestSharp.Extensions; using System.Globalization; -using System.Xml; +using System.Xml; +using System.ComponentModel; namespace RestSharp.Deserializers { @@ -124,13 +125,13 @@ private void Map(object x, XElement root) // check for nullable and extract underlying type if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) { - // if the value is empty, set the property to null... - if (value == null || String.IsNullOrEmpty(value.ToString())) - { - prop.SetValue(x, null, null); - continue; - } - type = type.GetGenericArguments()[0]; + // if the value is empty, set the property to null... + if (value == null || String.IsNullOrEmpty(value.ToString())) + { + prop.SetValue(x, null, null); + continue; + } + type = type.GetGenericArguments()[0]; } if (type == typeof(bool)) @@ -168,6 +169,33 @@ private void Map(object x, XElement root) } prop.SetValue(x, value, null); + } + else if (type == typeof(DateTimeOffset)) + { + var toConvert = value.ToString(); + if (!string.IsNullOrEmpty(toConvert)) + { + DateTimeOffset deserialisedValue; + try + { + deserialisedValue = XmlConvert.ToDateTimeOffset(toConvert); + prop.SetValue(x, deserialisedValue, null); + } + catch (Exception) + { + object result; + if (TryGetFromString(toConvert, out result, type)) + { + prop.SetValue(x, result, null); + } + else + { + //fallback to parse + deserialisedValue = DateTimeOffset.Parse(toConvert); + prop.SetValue(x, deserialisedValue, null); + } + } + } } else if (type == typeof(Decimal)) { @@ -179,12 +207,12 @@ private void Map(object x, XElement root) var raw = value.ToString(); value = string.IsNullOrEmpty(raw) ? Guid.Empty : new Guid(value.ToString()); prop.SetValue(x, value, null); - } - else if (type == typeof(TimeSpan)) - { - var timeSpan = XmlConvert.ToTimeSpan(value.ToString()); - prop.SetValue(x, timeSpan, null); - } + } + else if (type == typeof(TimeSpan)) + { + var timeSpan = XmlConvert.ToTimeSpan(value.ToString()); + prop.SetValue(x, timeSpan, null); + } else if (type.IsGenericType) { var t = type.GetGenericArguments()[0]; @@ -209,19 +237,40 @@ private void Map(object x, XElement root) prop.SetValue(x, list, null); } else - { - // nested property classes - if (root != null) - { - var element = GetElementByName(root, name); - if (element != null) - { - var item = CreateAndMap(type, element); - prop.SetValue(x, item, null); - } + { + //fallback to type converters if possible + object result; + if (TryGetFromString(value.ToString(), out result, type)) + { + prop.SetValue(x, result, null); + } + else + { + // nested property classes + if (root != null) + { + var element = GetElementByName(root, name); + if (element != null) + { + var item = CreateAndMap(type, element); + prop.SetValue(x, item, null); + } + } } } } + } + + private static bool TryGetFromString(string inputString, out object result, Type type) + { + var converter = TypeDescriptor.GetConverter(type); + if (converter.CanConvertFrom(typeof(string))) + { + result = (converter.ConvertFromInvariantString(inputString)); + return true; + } + result = null; + return false; } private void PopulateListFromElements(Type t, IEnumerable elements, IList list)