From 77befa1ae4b201281f2501d1398d447499d53fc6 Mon Sep 17 00:00:00 2001 From: Pedro Lamas Date: Fri, 20 Jul 2012 16:57:39 +0100 Subject: [PATCH] Fixed List json deserialization, when T was a something like DateTime, Timespan, Guid, etc. --- RestSharp.Tests/JsonTests.cs | 34 ++ RestSharp.Tests/SampleClasses/misc.cs | 372 ++++++++++---------- RestSharp/Deserializers/JsonDeserializer.cs | 227 ++++++------ 3 files changed, 322 insertions(+), 311 deletions(-) diff --git a/RestSharp.Tests/JsonTests.cs b/RestSharp.Tests/JsonTests.cs index 08b9d0697..9eb5e310b 100644 --- a/RestSharp.Tests/JsonTests.cs +++ b/RestSharp.Tests/JsonTests.cs @@ -128,6 +128,40 @@ public void Can_Deserialize_Generic_Members() Assert.Equal("Foe sho", output.Data.Items[0].Nickname); } + [Fact] + public void Can_Deserialize_List_of_Guid() + { + Guid ID1 = new Guid("b0e5c11f-e944-478c-aadd-753b956d0c8c"); + Guid ID2 = new Guid("809399fa-21c4-4dca-8dcd-34cb697fbca0"); + var data = new JObject(); + data["Ids"] = new JArray(ID1, ID2); + + var d = new JsonDeserializer(); + var response = new RestResponse { Content = data.ToString() }; + var p = d.Deserialize(response); + + Assert.Equal(2, p.Ids.Count); + Assert.Equal(ID1, p.Ids[0]); + Assert.Equal(ID2, p.Ids[1]); + } + + [Fact] + public void Can_Deserialize_Generic_List_of_DateTime() + { + DateTime Item1 = new DateTime(2010, 2, 8, 11, 11, 11); + DateTime Item2 = Item1.AddSeconds(12345); + var data = new JObject(); + data["Items"] = new JArray(Item1.ToString("u"), Item2.ToString("u")); + + var d = new JsonDeserializer(); + var response = new RestResponse { Content = data.ToString() }; + var p = d.Deserialize>(response); + + Assert.Equal(2, p.Items.Count); + Assert.Equal(Item1, p.Items[0]); + Assert.Equal(Item2, p.Items[1]); + } + [Fact] public void Can_Deserialize_Empty_Elements_to_Nullable_Values() { diff --git a/RestSharp.Tests/SampleClasses/misc.cs b/RestSharp.Tests/SampleClasses/misc.cs index 7de441c08..f8ad380d1 100644 --- a/RestSharp.Tests/SampleClasses/misc.cs +++ b/RestSharp.Tests/SampleClasses/misc.cs @@ -1,183 +1,189 @@ -#region Licensed -// Copyright 2010 John Sheehan -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -#endregion - -using System; -using System.Collections.Generic; -using RestSharp.Serializers; - -namespace RestSharp.Tests -{ - public class PersonForXml - { - public string Name { get; set; } - public DateTime StartDate { get; set; } - public int Age { get; set; } - public decimal Percent { get; set; } - public long BigNumber { get; set; } - public bool IsCool { get; set; } - public List Friends { get; set; } - public Friend BestFriend { get; set; } - - protected string Ignore { get; set; } - public string IgnoreProxy { get { return Ignore; } } - - protected string ReadOnly { get { return null; } } - public string ReadOnlyProxy { get { return ReadOnly; } } - - public FoeList Foes { get; set; } - - public Guid UniqueId { get; set; } - public Guid EmptyGuid { get; set; } - - public Uri Url { get; set; } - public Uri UrlPath { get; set; } - - public Order Order { get; set; } - - public Disposition Disposition { get; set; } - - } - - public class IncomingInvoice - { - public int ConceptId { get; set; } - } - - public class PersonForJson - { - public string Name { get; set; } - public DateTime StartDate { get; set; } - public int Age { get; set; } - public decimal Percent { get; set; } - public long BigNumber { get; set; } - public bool IsCool { get; set; } - public List Friends { get; set; } - public Friend BestFriend { get; set; } - public Guid Guid { get; set; } - public Guid EmptyGuid { get; set; } - public Uri Url { get; set; } - public Uri UrlPath { get; set; } - - protected string Ignore { get; set; } - public string IgnoreProxy { get { return Ignore; } } - - protected string ReadOnly { get { return null; } } - public string ReadOnlyProxy { get { return ReadOnly; } } - - public Dictionary Foes { get; set; } - - public Order Order { get; set; } - - public Disposition Disposition { get; set; } - } - - public enum Order { - First, - Second, - Third - } - - public enum Disposition - { - Friendly, - SoSo, - SteerVeryClear - } - - public class Friend - { - public string Name { get; set; } - public int Since { get; set; } - } - - public class Foe - { - public string Nickname { get; set; } - } - - public class FoeList : List - { - public string Team { get; set; } - } - - public class Birthdate - { - public DateTime Value { get; set; } - } - - public class OrderedProperties - { - [SerializeAs(Index = 2)] - public string Name { get; set; } - [SerializeAs(Index = 3)] - public int Age { get; set; } - [SerializeAs(Index = 1)] - public DateTime StartDate { get; set; } - } - - public class DatabaseCollection : List - { - } - - public class Database - { - public string Name { get; set; } - public string InitialCatalog { get; set; } - public string DataSource { get; set; } - } - - public class Generic - { - public T Data { get; set; } - } - - public class GenericWithList - { - public List Items { get; set; } - } - - public class DateTimeTestStructure - { - public DateTime DateTime { get; set; } - public DateTime? NullableDateTimeWithNull { get; set; } - public DateTime? NullableDateTimeWithValue { get; set; } - public DateTimeOffset DateTimeOffset { get; set; } - public DateTimeOffset? NullableDateTimeOffsetWithNull { get; set; } - public DateTimeOffset? NullableDateTimeOffsetWithValue { get; set; } - } - - public class TimeSpanTestStructure - { - public TimeSpan Tick { get; set; } - public TimeSpan Millisecond { get; set; } - public TimeSpan Second { get; set; } - public TimeSpan Minute { get; set; } - public TimeSpan Hour { get; set; } - public TimeSpan? NullableWithoutValue { get; set; } - public TimeSpan? NullableWithValue { get; set; } - } - - public class JsonEnumsTestStructure - { - public Disposition Upper { get; set; } - public Disposition Lower { get; set; } - public Disposition CamelCased { get; set; } - public Disposition Underscores { get; set; } - public Disposition LowerUnderscores { get; set; } - public Disposition Dashes { get; set; } - public Disposition LowerDashes { get; set; } - } -} +#region Licensed +// Copyright 2010 John Sheehan +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#endregion + +using System; +using System.Collections.Generic; +using RestSharp.Serializers; + +namespace RestSharp.Tests +{ + public class PersonForXml + { + public string Name { get; set; } + public DateTime StartDate { get; set; } + public int Age { get; set; } + public decimal Percent { get; set; } + public long BigNumber { get; set; } + public bool IsCool { get; set; } + public List Friends { get; set; } + public Friend BestFriend { get; set; } + + protected string Ignore { get; set; } + public string IgnoreProxy { get { return Ignore; } } + + protected string ReadOnly { get { return null; } } + public string ReadOnlyProxy { get { return ReadOnly; } } + + public FoeList Foes { get; set; } + + public Guid UniqueId { get; set; } + public Guid EmptyGuid { get; set; } + + public Uri Url { get; set; } + public Uri UrlPath { get; set; } + + public Order Order { get; set; } + + public Disposition Disposition { get; set; } + + } + + public class IncomingInvoice + { + public int ConceptId { get; set; } + } + + public class PersonForJson + { + public string Name { get; set; } + public DateTime StartDate { get; set; } + public int Age { get; set; } + public decimal Percent { get; set; } + public long BigNumber { get; set; } + public bool IsCool { get; set; } + public List Friends { get; set; } + public Friend BestFriend { get; set; } + public Guid Guid { get; set; } + public Guid EmptyGuid { get; set; } + public Uri Url { get; set; } + public Uri UrlPath { get; set; } + + protected string Ignore { get; set; } + public string IgnoreProxy { get { return Ignore; } } + + protected string ReadOnly { get { return null; } } + public string ReadOnlyProxy { get { return ReadOnly; } } + + public Dictionary Foes { get; set; } + + public Order Order { get; set; } + + public Disposition Disposition { get; set; } + } + + public enum Order + { + First, + Second, + Third + } + + public enum Disposition + { + Friendly, + SoSo, + SteerVeryClear + } + + public class Friend + { + public string Name { get; set; } + public int Since { get; set; } + } + + public class Foe + { + public string Nickname { get; set; } + } + + public class FoeList : List + { + public string Team { get; set; } + } + + public class Birthdate + { + public DateTime Value { get; set; } + } + + public class OrderedProperties + { + [SerializeAs(Index = 2)] + public string Name { get; set; } + [SerializeAs(Index = 3)] + public int Age { get; set; } + [SerializeAs(Index = 1)] + public DateTime StartDate { get; set; } + } + + public class DatabaseCollection : List + { + } + + public class Database + { + public string Name { get; set; } + public string InitialCatalog { get; set; } + public string DataSource { get; set; } + } + + public class Generic + { + public T Data { get; set; } + } + + public class GenericWithList + { + public List Items { get; set; } + } + + public class GuidList + { + public List Ids { get; set; } + } + + public class DateTimeTestStructure + { + public DateTime DateTime { get; set; } + public DateTime? NullableDateTimeWithNull { get; set; } + public DateTime? NullableDateTimeWithValue { get; set; } + public DateTimeOffset DateTimeOffset { get; set; } + public DateTimeOffset? NullableDateTimeOffsetWithNull { get; set; } + public DateTimeOffset? NullableDateTimeOffsetWithValue { get; set; } + } + + public class TimeSpanTestStructure + { + public TimeSpan Tick { get; set; } + public TimeSpan Millisecond { get; set; } + public TimeSpan Second { get; set; } + public TimeSpan Minute { get; set; } + public TimeSpan Hour { get; set; } + public TimeSpan? NullableWithoutValue { get; set; } + public TimeSpan? NullableWithValue { get; set; } + } + + public class JsonEnumsTestStructure + { + public Disposition Upper { get; set; } + public Disposition Lower { get; set; } + public Disposition CamelCased { get; set; } + public Disposition Underscores { get; set; } + public Disposition LowerUnderscores { get; set; } + public Disposition Dashes { get; set; } + public Disposition LowerDashes { get; set; } + } +} diff --git a/RestSharp/Deserializers/JsonDeserializer.cs b/RestSharp/Deserializers/JsonDeserializer.cs index cc3f654e3..d28215739 100644 --- a/RestSharp/Deserializers/JsonDeserializer.cs +++ b/RestSharp/Deserializers/JsonDeserializer.cs @@ -1,10 +1,9 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Globalization; using System.Linq; - using RestSharp.Extensions; -using System.Globalization; namespace RestSharp.Deserializers { @@ -78,107 +77,14 @@ private void Map(object target, IDictionary data) if (value == null) continue; - var stringValue = Convert.ToString(value, Culture); - // check for nullable and extract underlying type if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) { type = type.GetGenericArguments()[0]; } - if (type.IsPrimitive) - { - // no primitives can contain quotes so we can safely remove them - // allows converting a json value like {"index": "1"} to an int - var tmpVal = stringValue.Replace("\"", string.Empty); - prop.SetValue(target, tmpVal.ChangeType(type, Culture), null); - } - else if (type.IsEnum) - { - var converted = type.FindEnumValue(stringValue, Culture); - prop.SetValue(target, converted, null); - } - else if (type == typeof(Uri)) - { - var uri = new Uri(stringValue, UriKind.RelativeOrAbsolute); - prop.SetValue(target, uri, null); - } - else if (type == typeof(string)) - { - prop.SetValue(target, stringValue, null); - } - else if (type == typeof(DateTime) || type == typeof(DateTimeOffset)) - { - DateTime dt; - if (DateFormat.HasValue()) - { - dt = DateTime.ParseExact(stringValue, DateFormat, Culture); - } - else - { - // try parsing instead - dt = stringValue.ParseJsonDate(Culture); - } - - if (type == typeof(DateTime)) - { - prop.SetValue(target, dt, null); - } - else if (type == typeof(DateTimeOffset)) - { - prop.SetValue(target, (DateTimeOffset)dt, null); - } - } - else if (type == typeof(Decimal)) - { - var dec = Decimal.Parse(stringValue, Culture); - prop.SetValue(target, dec, null); - } - else if (type == typeof(Guid)) - { - var guid = string.IsNullOrEmpty(stringValue) ? Guid.Empty : new Guid(stringValue); - prop.SetValue(target, guid, null); - } - else if (type == typeof(TimeSpan)) - { - var timeSpan = TimeSpan.Parse(stringValue); - prop.SetValue(target, timeSpan, null); - } - else if (type.IsGenericType) - { - var genericTypeDef = type.GetGenericTypeDefinition(); - if (genericTypeDef == typeof(List<>)) - { - var list = BuildList(type, value); - prop.SetValue(target, list, null); - } - else if (genericTypeDef == typeof(Dictionary<,>)) - { - var keyType = type.GetGenericArguments()[0]; - - // only supports Dict() - if (keyType == typeof(string)) - { - var dict = BuildDictionary(type, value); - prop.SetValue(target, dict, null); - } - } - else - { - // nested property classes - var item = CreateAndMap(type, data[actualName]); - prop.SetValue(target, item, null); - } - } - else - { - // nested property classes - var item = CreateAndMap(type, data[actualName]); - prop.SetValue(target, item, null); - } + prop.SetValue(target, ConvertValue(type, value), null); } - - } private IDictionary BuildDictionary(Type type, object parent) @@ -188,7 +94,7 @@ private IDictionary BuildDictionary(Type type, object parent) foreach (var child in (IDictionary)parent) { var key = child.Key; - var item = CreateAndMap(valueType, child.Value); + var item = ConvertValue(valueType, child.Value); dict.Add(key, item); } @@ -201,77 +107,142 @@ private IList BuildList(Type type, object parent) var listType = type.GetInterfaces().First(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IList<>)); var itemType = listType.GetGenericArguments()[0]; - if (parent is IList) { - foreach (var element in (IList)parent) { + if (parent is IList) + { + foreach (var element in (IList)parent) + { if (itemType.IsPrimitive) { - var value = element.ToString (); - list.Add (value.ChangeType (itemType, Culture)); + var value = element.ToString(); + list.Add(value.ChangeType(itemType, Culture)); } - else if (itemType == typeof (string)) + else if (itemType == typeof(string)) { if (element == null) { - list.Add (null); + list.Add(null); continue; } - list.Add (element.ToString ()); + list.Add(element.ToString()); } else { if (element == null) { - list.Add (null); + list.Add(null); continue; } - var item = CreateAndMap (itemType, element); - list.Add (item); + var item = ConvertValue(itemType, element); + list.Add(item); } } - } else { - list.Add (CreateAndMap (itemType, parent)); + } + else + { + list.Add(ConvertValue(itemType, parent)); } return list; } - private object CreateAndMap(Type type, object element) + private object ConvertValue(Type type, object value) { - object instance = null; - if (type.IsGenericType) + var stringValue = Convert.ToString(value, Culture); + + if (type.IsPrimitive) { - var genericTypeDef = type.GetGenericTypeDefinition(); - if (genericTypeDef == typeof(Dictionary<,>)) + // no primitives can contain quotes so we can safely remove them + // allows converting a json value like {"index": "1"} to an int + var tmpVal = stringValue.Replace("\"", string.Empty); + + return tmpVal.ChangeType(type, Culture); + } + else if (type.IsEnum) + { + return type.FindEnumValue(stringValue, Culture); + } + else if (type == typeof(Uri)) + { + return new Uri(stringValue, UriKind.RelativeOrAbsolute); + } + else if (type == typeof(string)) + { + return stringValue; + } + else if (type == typeof(DateTime) || type == typeof(DateTimeOffset)) + { + DateTime dt; + if (DateFormat.HasValue()) { - instance = BuildDictionary(type, element); + dt = DateTime.ParseExact(stringValue, DateFormat, Culture); } - else if (genericTypeDef == typeof(List<>)) + else { - instance = BuildList(type, element); + // try parsing instead + dt = stringValue.ParseJsonDate(Culture); } - else if (type == typeof(string)) + + if (type == typeof(DateTime)) { - instance = (string)element; + return dt; } - else + else if (type == typeof(DateTimeOffset)) { - instance = Activator.CreateInstance(type); - Map(instance, (IDictionary)element); + return (DateTimeOffset)dt; } } - else if (type == typeof(string)) + else if (type == typeof(Decimal)) + { + return Decimal.Parse(stringValue, Culture); + } + else if (type == typeof(Guid)) + { + return string.IsNullOrEmpty(stringValue) ? Guid.Empty : new Guid(stringValue); + } + else if (type == typeof(TimeSpan)) { - instance = element.ToString(); + return TimeSpan.Parse(stringValue); + } + else if (type.IsGenericType) + { + var genericTypeDef = type.GetGenericTypeDefinition(); + if (genericTypeDef == typeof(List<>)) + { + return BuildList(type, value); + } + else if (genericTypeDef == typeof(Dictionary<,>)) + { + var keyType = type.GetGenericArguments()[0]; + + // only supports Dict() + if (keyType == typeof(string)) + { + return BuildDictionary(type, value); + } + } + else + { + // nested property classes + return CreateAndMap(type, value); + } } else { - instance = Activator.CreateInstance(type); - var data = (IDictionary)element; - Map(instance, data); + // nested property classes + return CreateAndMap(type, value); } - return instance; + + return null; } + private object CreateAndMap(Type type, object element) + { + var instance = Activator.CreateInstance(type); + + Map(instance, (IDictionary)element); + + return instance; + } } } \ No newline at end of file