Skip to content

Commit 15e6efb

Browse files
committed
Merge pull request #407 from restsharp/datetimeoffset-support
Datetimeoffset support
2 parents 405ff6c + ec4405a commit 15e6efb

File tree

3 files changed

+156
-71
lines changed

3 files changed

+156
-71
lines changed

RestSharp.Tests/XmlTests.cs

Lines changed: 82 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -254,47 +254,47 @@ public void Can_Deserialize_Elements_to_Nullable_Values()
254254
Assert.Equal(new Guid(GuidString), output.UniqueId);
255255
}
256256

257-
[Fact]
258-
public void Can_Deserialize_TimeSpan()
259-
{
260-
var culture = CultureInfo.InvariantCulture;
261-
var doc = new XDocument(culture);
262-
263-
TimeSpan? nullTimespan = null;
264-
TimeSpan? nullValueTimeSpan = new TimeSpan(21, 30, 7);
265-
266-
var root = new XElement("Person");
267-
root.Add(new XElement("Tick", new TimeSpan(468006)));
268-
root.Add(new XElement("Millisecond", new TimeSpan(0, 0, 0, 0, 125)));
269-
root.Add(new XElement("Second", new TimeSpan(0, 0, 8)));
270-
root.Add(new XElement("Minute", new TimeSpan(0, 55, 2)));
271-
root.Add(new XElement("Hour", new TimeSpan(21, 30, 7)));
272-
root.Add(new XElement("NullableWithoutValue", nullTimespan));
273-
root.Add(new XElement("NullableWithValue", nullValueTimeSpan));
274-
275-
doc.Add(root);
276-
277-
var xml = new XmlDeserializer
278-
{
279-
Culture = culture,
280-
};
281-
282-
var response = new RestResponse { Content = doc.ToString() };
283-
284-
var d = new XmlDeserializer()
285-
{
286-
Culture = culture,
287-
};
288-
var payload = d.Deserialize<TimeSpanTestStructure>(response);
289-
Assert.Equal(new TimeSpan(468006), payload.Tick);
290-
Assert.Equal(new TimeSpan(0, 0, 0, 0, 125), payload.Millisecond);
291-
Assert.Equal(new TimeSpan(0, 0, 8), payload.Second);
292-
Assert.Equal(new TimeSpan(0, 55, 2), payload.Minute);
293-
Assert.Equal(new TimeSpan(21, 30, 7), payload.Hour);
294-
Assert.Null(payload.NullableWithoutValue);
295-
Assert.NotNull(payload.NullableWithValue);
296-
Assert.Equal(new TimeSpan(21, 30, 7), payload.NullableWithValue.Value);
297-
}
257+
[Fact]
258+
public void Can_Deserialize_TimeSpan()
259+
{
260+
var culture = CultureInfo.InvariantCulture;
261+
var doc = new XDocument(culture);
262+
263+
TimeSpan? nullTimespan = null;
264+
TimeSpan? nullValueTimeSpan = new TimeSpan(21, 30, 7);
265+
266+
var root = new XElement("Person");
267+
root.Add(new XElement("Tick", new TimeSpan(468006)));
268+
root.Add(new XElement("Millisecond", new TimeSpan(0, 0, 0, 0, 125)));
269+
root.Add(new XElement("Second", new TimeSpan(0, 0, 8)));
270+
root.Add(new XElement("Minute", new TimeSpan(0, 55, 2)));
271+
root.Add(new XElement("Hour", new TimeSpan(21, 30, 7)));
272+
root.Add(new XElement("NullableWithoutValue", nullTimespan));
273+
root.Add(new XElement("NullableWithValue", nullValueTimeSpan));
274+
275+
doc.Add(root);
276+
277+
var xml = new XmlDeserializer
278+
{
279+
Culture = culture,
280+
};
281+
282+
var response = new RestResponse { Content = doc.ToString() };
283+
284+
var d = new XmlDeserializer()
285+
{
286+
Culture = culture,
287+
};
288+
var payload = d.Deserialize<TimeSpanTestStructure>(response);
289+
Assert.Equal(new TimeSpan(468006), payload.Tick);
290+
Assert.Equal(new TimeSpan(0, 0, 0, 0, 125), payload.Millisecond);
291+
Assert.Equal(new TimeSpan(0, 0, 8), payload.Second);
292+
Assert.Equal(new TimeSpan(0, 55, 2), payload.Minute);
293+
Assert.Equal(new TimeSpan(21, 30, 7), payload.Hour);
294+
Assert.Null(payload.NullableWithoutValue);
295+
Assert.NotNull(payload.NullableWithValue);
296+
Assert.Equal(new TimeSpan(21, 30, 7), payload.NullableWithValue.Value);
297+
}
298298

299299
[Fact]
300300
public void Can_Deserialize_Custom_Formatted_Date()
@@ -496,8 +496,8 @@ public void Can_Deserialize_Names_With_Underscores_Without_Matching_Case_On_Defa
496496
Assert.Equal (5, p.Foes.Count);
497497
Assert.Equal ("Yankees", p.Foes.Team);
498498
}
499-
500-
[Fact]
499+
500+
[Fact]
501501
public void Can_Deserialize_Lower_Cased_Root_Elements_With_Dashes()
502502
{
503503
var doc = CreateDashesXml();
@@ -640,6 +640,41 @@ public void Can_Deserialize_Mixture_Of_Empty_Elements_With_Attributes_And_Popula
640640
Assert.Null(output.Id);
641641
Assert.Null(output.StartDate);
642642
Assert.Equal(new Guid(GuidString), output.UniqueId);
643+
}
644+
645+
[Fact]
646+
public void Can_Deserialize_DateTimeOffset()
647+
{
648+
var culture = CultureInfo.InvariantCulture;
649+
var doc = new XDocument(culture);
650+
651+
DateTimeOffset DateTimeOffset = new DateTimeOffset(2013, 02, 08, 9, 18, 22, TimeSpan.FromHours(10));
652+
DateTimeOffset? NullableDateTimeOffsetWithValue = new DateTimeOffset(2013, 02, 08, 9, 18, 23, TimeSpan.FromHours(10));
653+
654+
var root = new XElement("Dates");
655+
root.Add(new XElement("DateTimeOffset", DateTimeOffset));
656+
root.Add(new XElement("NullableDateTimeOffsetWithNull", string.Empty));
657+
root.Add(new XElement("NullableDateTimeOffsetWithValue", NullableDateTimeOffsetWithValue));
658+
659+
doc.Add(root);
660+
661+
var xml = new XmlDeserializer
662+
{
663+
Culture = culture,
664+
};
665+
666+
var response = new RestResponse { Content = doc.ToString() };
667+
668+
var d = new XmlDeserializer()
669+
{
670+
Culture = culture,
671+
};
672+
var payload = d.Deserialize<DateTimeTestStructure>(response);
673+
Assert.Equal(DateTimeOffset, payload.DateTimeOffset);
674+
Assert.Null(payload.NullableDateTimeOffsetWithNull);
675+
676+
Assert.True(payload.NullableDateTimeOffsetWithValue.HasValue);
677+
Assert.Equal(NullableDateTimeOffsetWithValue, payload.NullableDateTimeOffsetWithValue);
643678
}
644679

645680
private static string CreateUnderscoresXml()
@@ -771,10 +806,10 @@ private static string CreateDashesXml()
771806
doc.Add(root);
772807
return doc.ToString();
773808
}
774-
775-
private static string CreateLowerCasedRootElementWithDashesXml()
809+
810+
private static string CreateLowerCasedRootElementWithDashesXml()
776811
{
777-
var doc = new XDocument();
812+
var doc = new XDocument();
778813
var root = new XElement("incoming-invoices",
779814
new XElement("incoming-invoice",
780815
new XElement("concept-id", 45)
@@ -934,5 +969,6 @@ private static string CreateXmlWithAttributesAndNullValuesAndPopulatedValues()
934969

935970
return doc.ToString();
936971
}
972+
937973
}
938974
}

RestSharp/Deserializers/XmlAttributeDeserializer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public T Deserialize<T>(IRestResponse response)
5555
RemoveNamespace(doc);
5656
}
5757

58-
var x = Activator.CreateInstance<T>();
58+
var x = Activator.CreateInstance<T>();
5959
var objType = x.GetType();
6060

6161
if (objType.IsSubclassOfRawGeneric(typeof(List<>)))

RestSharp/Deserializers/XmlDeserializer.cs

Lines changed: 73 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222

2323
using RestSharp.Extensions;
2424
using System.Globalization;
25-
using System.Xml;
25+
using System.Xml;
26+
using System.ComponentModel;
2627

2728
namespace RestSharp.Deserializers
2829
{
@@ -124,13 +125,13 @@ private void Map(object x, XElement root)
124125
// check for nullable and extract underlying type
125126
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
126127
{
127-
// if the value is empty, set the property to null...
128-
if (value == null || String.IsNullOrEmpty(value.ToString()))
129-
{
130-
prop.SetValue(x, null, null);
131-
continue;
132-
}
133-
type = type.GetGenericArguments()[0];
128+
// if the value is empty, set the property to null...
129+
if (value == null || String.IsNullOrEmpty(value.ToString()))
130+
{
131+
prop.SetValue(x, null, null);
132+
continue;
133+
}
134+
type = type.GetGenericArguments()[0];
134135
}
135136

136137
if (type == typeof(bool))
@@ -168,6 +169,33 @@ private void Map(object x, XElement root)
168169
}
169170

170171
prop.SetValue(x, value, null);
172+
}
173+
else if (type == typeof(DateTimeOffset))
174+
{
175+
var toConvert = value.ToString();
176+
if (!string.IsNullOrEmpty(toConvert))
177+
{
178+
DateTimeOffset deserialisedValue;
179+
try
180+
{
181+
deserialisedValue = XmlConvert.ToDateTimeOffset(toConvert);
182+
prop.SetValue(x, deserialisedValue, null);
183+
}
184+
catch (Exception)
185+
{
186+
object result;
187+
if (TryGetFromString(toConvert, out result, type))
188+
{
189+
prop.SetValue(x, result, null);
190+
}
191+
else
192+
{
193+
//fallback to parse
194+
deserialisedValue = DateTimeOffset.Parse(toConvert);
195+
prop.SetValue(x, deserialisedValue, null);
196+
}
197+
}
198+
}
171199
}
172200
else if (type == typeof(Decimal))
173201
{
@@ -179,12 +207,12 @@ private void Map(object x, XElement root)
179207
var raw = value.ToString();
180208
value = string.IsNullOrEmpty(raw) ? Guid.Empty : new Guid(value.ToString());
181209
prop.SetValue(x, value, null);
182-
}
183-
else if (type == typeof(TimeSpan))
184-
{
185-
var timeSpan = XmlConvert.ToTimeSpan(value.ToString());
186-
prop.SetValue(x, timeSpan, null);
187-
}
210+
}
211+
else if (type == typeof(TimeSpan))
212+
{
213+
var timeSpan = XmlConvert.ToTimeSpan(value.ToString());
214+
prop.SetValue(x, timeSpan, null);
215+
}
188216
else if (type.IsGenericType)
189217
{
190218
var t = type.GetGenericArguments()[0];
@@ -209,19 +237,40 @@ private void Map(object x, XElement root)
209237
prop.SetValue(x, list, null);
210238
}
211239
else
212-
{
213-
// nested property classes
214-
if (root != null)
215-
{
216-
var element = GetElementByName(root, name);
217-
if (element != null)
218-
{
219-
var item = CreateAndMap(type, element);
220-
prop.SetValue(x, item, null);
221-
}
240+
{
241+
//fallback to type converters if possible
242+
object result;
243+
if (TryGetFromString(value.ToString(), out result, type))
244+
{
245+
prop.SetValue(x, result, null);
246+
}
247+
else
248+
{
249+
// nested property classes
250+
if (root != null)
251+
{
252+
var element = GetElementByName(root, name);
253+
if (element != null)
254+
{
255+
var item = CreateAndMap(type, element);
256+
prop.SetValue(x, item, null);
257+
}
258+
}
222259
}
223260
}
224261
}
262+
}
263+
264+
private static bool TryGetFromString(string inputString, out object result, Type type)
265+
{
266+
var converter = TypeDescriptor.GetConverter(type);
267+
if (converter.CanConvertFrom(typeof(string)))
268+
{
269+
result = (converter.ConvertFromInvariantString(inputString));
270+
return true;
271+
}
272+
result = null;
273+
return false;
225274
}
226275

227276
private void PopulateListFromElements(Type t, IEnumerable<XElement> elements, IList list)

0 commit comments

Comments
 (0)