From 000700685098c1a4337470aa6792186dfad6536b Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Sat, 30 Mar 2013 20:46:11 +1300 Subject: [PATCH] -Added support for deserializing IList -Fixed bugs --- .../Properties/AssemblyInfo.cs | 2 +- .../Serialization/JsonSerializerTest.cs | 49 ++++++++-- .../SerializationErrorHandlingTests.cs | 28 +++--- .../Serialization/TypeNameHandlingTests.cs | 2 +- .../Properties/AssemblyInfo.cs | 2 +- .../Serialization/JsonArrayContract.cs | 94 +++++++------------ .../Serialization/JsonDictionaryContract.cs | 48 ++-------- .../JsonSerializerInternalReader.cs | 7 +- .../JsonSerializerInternalWriter.cs | 6 +- .../Utilities/ReflectionUtils.cs | 9 ++ 10 files changed, 118 insertions(+), 129 deletions(-) diff --git a/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs index f3ed21b2..634c0c70 100644 --- a/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs +++ b/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs @@ -75,4 +75,4 @@ using System.Security; // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: [assembly: AssemblyVersion("4.5.0.0")] -[assembly: AssemblyFileVersion("5.0.1.15929")] +[assembly: AssemblyFileVersion("5.0.1.15930")] diff --git a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs index 6e8daa3a..d97c5f4f 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs @@ -4186,8 +4186,16 @@ To fix this error either change the environment to be fully trusted, change the 4.3, null ]", jsonText); + } - double?[] d = (double?[])JsonConvert.DeserializeObject(jsonText, typeof(double?[])); + [Test] + public void DeserializeNullableArray() + { + double?[] d = (double?[])JsonConvert.DeserializeObject(@"[ + 2.4, + 4.3, + null +]", typeof(double?[])); Assert.AreEqual(3, d.Length); Assert.AreEqual(2.4, d[0]); @@ -6747,17 +6755,17 @@ Parameter name: value", public void DeserializeIEnumerableFromConstructor() { string json = @"[ - ""One"", - ""II"", - ""3"" + 1, + 2, + null ]"; - var result = JsonConvert.DeserializeObject>(json); + var result = JsonConvert.DeserializeObject>(json); Assert.AreEqual(3, result.Count()); - Assert.AreEqual("One", result.ElementAt(0)); - Assert.AreEqual("II", result.ElementAt(1)); - Assert.AreEqual("3", result.ElementAt(2)); + Assert.AreEqual(1, result.ElementAt(0)); + Assert.AreEqual(2, result.ElementAt(1)); + Assert.AreEqual(null, result.ElementAt(2)); } public class EnumerableClassFailure : IEnumerable @@ -6859,6 +6867,20 @@ Parameter name: value", Assert.AreEqual(BigInteger.Parse("9000000000000000000000000000000000000000000000000"), nineQuindecillion); } + + [Test] + public void DeserializeReadOnlyListWithNullableType() + { + string json = @"[ + 1, + null + ]"; + + var l = JsonConvert.DeserializeObject>(json); + + Assert.AreEqual(1, l[0]); + Assert.AreEqual(null, l[1]); + } #endif [Test] @@ -7787,7 +7809,18 @@ Parameter name: value", } #endif + [Test] + public void DeserializeNonGenericList() + { + IList l = JsonConvert.DeserializeObject("['string!']"); + + Assert.AreEqual(typeof(List), l.GetType()); + Assert.AreEqual(1, l.Count); + Assert.AreEqual("string!", l[0]); + } + #if !(NET20 || NET35 || SILVERLIGHT || PORTABLE) + [Test] public void SerializeBigInteger() { BigInteger i = BigInteger.Parse("123456789999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999990"); diff --git a/Src/Newtonsoft.Json.Tests/Serialization/SerializationErrorHandlingTests.cs b/Src/Newtonsoft.Json.Tests/Serialization/SerializationErrorHandlingTests.cs index e311ac5b..092816ca 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/SerializationErrorHandlingTests.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/SerializationErrorHandlingTests.cs @@ -652,20 +652,20 @@ namespace Newtonsoft.Json.Tests.Serialization { JsonSerializerSettings settings = new JsonSerializerSettings(); settings.Error += (obj, args) => - { - args.ErrorContext.Handled = true; - }; - - var data = new List() { - new ErrorPerson2{FirstName = "Scott", LastName = "Hanselman"}, - new ErrorPerson2{FirstName = "Scott", LastName = "Hunter"}, - new ErrorPerson2{FirstName = "Scott", LastName = "Guthrie"}, + args.ErrorContext.Handled = true; }; - var dictionary = data.GroupBy(person => person.FirstName).ToDictionary(group => @group.Key, group => @group.Cast()); - var output = JsonConvert.SerializeObject(dictionary, Formatting.None, settings); - Assert.AreEqual(@"{""Scott"":null}", output); + var data = new List() + { + new ErrorPerson2 {FirstName = "Scott", LastName = "Hanselman"}, + new ErrorPerson2 {FirstName = "Scott", LastName = "Hunter"}, + new ErrorPerson2 {FirstName = "Scott", LastName = "Guthrie"}, + }; + + Dictionary> dictionary = data.GroupBy(person => person.FirstName).ToDictionary(group => @group.Key, group => @group.Cast()); + string output = JsonConvert.SerializeObject(dictionary, Formatting.None, settings); + Assert.AreEqual(@"{""Scott"":[]}", output); } [Test] @@ -685,10 +685,10 @@ namespace Newtonsoft.Json.Tests.Serialization new ErrorPerson2 {FirstName = "James", LastName = "Newton-King"}, }; - var dictionary = data.GroupBy(person => person.FirstName).ToDictionary(group => @group.Key, group => @group.Cast()); - var output = JsonConvert.SerializeObject(dictionary, Formatting.None, settings); + Dictionary> dictionary = data.GroupBy(person => person.FirstName).ToDictionary(group => @group.Key, group => @group.Cast()); + string output = JsonConvert.SerializeObject(dictionary, Formatting.None, settings); - Assert.AreEqual(@"{""Scott"":null,""James"":null}", output); + Assert.AreEqual(@"{""Scott"":[],""James"":[]}", output); } } diff --git a/Src/Newtonsoft.Json.Tests/Serialization/TypeNameHandlingTests.cs b/Src/Newtonsoft.Json.Tests/Serialization/TypeNameHandlingTests.cs index 4cc1be95..e6486457 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/TypeNameHandlingTests.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/TypeNameHandlingTests.cs @@ -867,7 +867,7 @@ namespace Newtonsoft.Json.Tests.Serialization [Test] public void SerializingIEnumerableOfTShouldRetainGenericTypeInfo() { - string productClassRef = ReflectionUtils.GetTypeName(typeof (Product[]), FormatterAssemblyStyle.Simple); + string productClassRef = ReflectionUtils.GetTypeName(typeof(CustomEnumerable), FormatterAssemblyStyle.Simple); CustomEnumerable products = new CustomEnumerable(); diff --git a/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs index 59c058ab..2f9bf83f 100644 --- a/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs +++ b/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs @@ -90,6 +90,6 @@ using System.Security; // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: [assembly: AssemblyVersion("4.5.0.0")] -[assembly: AssemblyFileVersion("5.0.1.15929")] +[assembly: AssemblyFileVersion("5.0.1.15930")] [assembly: CLSCompliant(true)] diff --git a/Src/Newtonsoft.Json/Serialization/JsonArrayContract.cs b/Src/Newtonsoft.Json/Serialization/JsonArrayContract.cs index 5961fddf..d5bbfa11 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonArrayContract.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonArrayContract.cs @@ -84,6 +84,7 @@ namespace Newtonsoft.Json.Serialization { CollectionItemType = ReflectionUtils.GetCollectionItemType(UnderlyingType); IsReadOnlyOrFixedSize = true; + _genericCollectionDefinitionType = ReflectionUtils.MakeGenericType(typeof (List<>), CollectionItemType); canDeserialize = true; IsMultidimensionalArray = (UnderlyingType.IsArray && UnderlyingType.GetArrayRank() > 1); @@ -104,6 +105,9 @@ namespace Newtonsoft.Json.Serialization else CollectionItemType = ReflectionUtils.GetCollectionItemType(underlyingType); + if (underlyingType == typeof (IList)) + CreatedType = typeof (List); + if (CollectionItemType != null) ParametrizedConstructor = CollectionUtils.ResolveEnumableCollectionConstructor(underlyingType, CollectionItemType); canDeserialize = true; @@ -111,6 +115,16 @@ namespace Newtonsoft.Json.Serialization else if (ReflectionUtils.ImplementsGenericDefinition(underlyingType, typeof(ICollection<>), out _genericCollectionDefinitionType)) { CollectionItemType = _genericCollectionDefinitionType.GetGenericArguments()[0]; + + if (ReflectionUtils.IsGenericDefinition(underlyingType, typeof(ICollection<>)) + || ReflectionUtils.IsGenericDefinition(underlyingType, typeof(IList<>))) + CreatedType = ReflectionUtils.MakeGenericType(typeof(List<>), CollectionItemType); + +#if !(NET20 || NET35) + if (ReflectionUtils.IsGenericDefinition(underlyingType, typeof(ISet<>))) + CreatedType = ReflectionUtils.MakeGenericType(typeof(HashSet<>), CollectionItemType); +#endif + ParametrizedConstructor = CollectionUtils.ResolveEnumableCollectionConstructor(underlyingType, CollectionItemType); canDeserialize = true; ShouldCreateWrapper = true; @@ -119,18 +133,24 @@ namespace Newtonsoft.Json.Serialization else if (ReflectionUtils.ImplementsGenericDefinition(underlyingType, typeof (IReadOnlyCollection<>), out tempCollectionType)) { CollectionItemType = underlyingType.GetGenericArguments()[0]; - ParametrizedConstructor = CollectionUtils.ResolveEnumableCollectionConstructor(underlyingType, CollectionItemType); - IsReadOnlyOrFixedSize = true; - ShouldCreateWrapper = !typeof(IList).IsAssignableFrom(underlyingType); - canDeserialize = (ResolveReadOnlyCollectionConstructor(underlyingType, CollectionItemType) != null - || underlyingType.GetGenericTypeDefinition() == typeof(IReadOnlyCollection<>) - || underlyingType.GetGenericTypeDefinition() == typeof(IReadOnlyList<>)); + if (ReflectionUtils.IsGenericDefinition(underlyingType, typeof (IReadOnlyCollection<>)) + || ReflectionUtils.IsGenericDefinition(underlyingType, typeof (IReadOnlyList<>))) + CreatedType = ReflectionUtils.MakeGenericType(typeof(ReadOnlyCollection<>), CollectionItemType); + + _genericCollectionDefinitionType = ReflectionUtils.MakeGenericType(typeof(List<>), CollectionItemType); + ParametrizedConstructor = CollectionUtils.ResolveEnumableCollectionConstructor(CreatedType, CollectionItemType); + IsReadOnlyOrFixedSize = true; + canDeserialize = (ParametrizedConstructor != null); } #endif else if (ReflectionUtils.ImplementsGenericDefinition(underlyingType, typeof (IEnumerable<>), out tempCollectionType)) { CollectionItemType = tempCollectionType.GetGenericArguments()[0]; + + if (ReflectionUtils.IsGenericDefinition(UnderlyingType, typeof(IEnumerable<>))) + CreatedType = ReflectionUtils.MakeGenericType(typeof(List<>), CollectionItemType); + ParametrizedConstructor = CollectionUtils.ResolveEnumableCollectionConstructor(underlyingType, CollectionItemType); if (underlyingType.GetGenericTypeDefinition() == typeof (IEnumerable<>)) @@ -143,6 +163,8 @@ namespace Newtonsoft.Json.Serialization } else { + _genericCollectionDefinitionType = ReflectionUtils.MakeGenericType(typeof(List<>), CollectionItemType); + IsReadOnlyOrFixedSize = true; ShouldCreateWrapper = true; canDeserialize = (ParametrizedConstructor != null); @@ -160,35 +182,20 @@ namespace Newtonsoft.Json.Serialization if (CollectionItemType != null) _isCollectionItemTypeNullableType = ReflectionUtils.IsNullableType(CollectionItemType); - if (IsTypeGenericCollectionInterface(UnderlyingType)) - CreatedType = ReflectionUtils.MakeGenericType(typeof(List<>), CollectionItemType); -#if !(NET40 || NET35 || NET20 || SILVERLIGHT || WINDOWS_PHONE || PORTABLE) - else if (IsTypeGenericReadOnlyCollectionInterface(UnderlyingType)) - CreatedType = ReflectionUtils.MakeGenericType(typeof(ReadOnlyCollection<>), CollectionItemType); -#endif -#if !(NET20 || NET35) - else if (IsTypeGenericSetInterface(UnderlyingType)) - CreatedType = ReflectionUtils.MakeGenericType(typeof(HashSet<>), CollectionItemType); -#endif - #if (NET20 || NET35) // bug in .NET 2.0 & 3.5 that List> throws an error when adding null via IList.Add(object) // wrapper will handle calling Add(T) instead - if (ReflectionUtils.InheritsGenericDefinition(CreatedType, typeof(List<>), out tempCollectionType)) + if (_isCollectionItemTypeNullableType + && (ReflectionUtils.InheritsGenericDefinition(CreatedType, typeof(List<>), out tempCollectionType) + || (CreatedType.IsArray && !IsMultidimensionalArray))) { - Type tempCollectionItemType = tempCollectionType.GetGenericArguments()[0]; - if (ReflectionUtils.IsNullableType(tempCollectionItemType)) - ShouldCreateWrapper = true; + ShouldCreateWrapper = true; } #endif } internal IWrappedCollection CreateWrapper(object list) { - if ((list is IList && (CollectionItemType == null || !_isCollectionItemTypeNullableType)) - || UnderlyingType.IsArray) - return new CollectionWrapper((IList)list); - if (_genericCollectionDefinitionType != null) { EnsureGenericWrapperCreator(); @@ -244,42 +251,5 @@ namespace Newtonsoft.Json.Serialization _genericWrapperCreator = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall(genericWrapperConstructor); } } - - private bool IsTypeGenericCollectionInterface(Type type) - { - if (!type.IsGenericType()) - return false; - - Type genericDefinition = type.GetGenericTypeDefinition(); - - return (genericDefinition == typeof(IList<>) - || genericDefinition == typeof(ICollection<>) - || genericDefinition == typeof(IEnumerable<>)); - } - -#if !(NET40 || NET35 || NET20 || SILVERLIGHT || WINDOWS_PHONE || PORTABLE) - private bool IsTypeGenericReadOnlyCollectionInterface(Type type) - { - if (!type.IsGenericType()) - return false; - - Type genericDefinition = type.GetGenericTypeDefinition(); - - return (genericDefinition == typeof (IReadOnlyCollection<>) - || genericDefinition == typeof (IReadOnlyList<>)); - } -#endif - -#if !(NET20 || NET35) - private bool IsTypeGenericSetInterface(Type type) - { - if (!type.IsGenericType()) - return false; - - Type genericDefinition = type.GetGenericTypeDefinition(); - - return (genericDefinition == typeof(ISet<>)); - } -#endif } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Serialization/JsonDictionaryContract.cs b/Src/Newtonsoft.Json/Serialization/JsonDictionaryContract.cs index 02845752..73753d1a 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonDictionaryContract.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonDictionaryContract.cs @@ -87,6 +87,9 @@ namespace Newtonsoft.Json.Serialization { keyType = _genericCollectionDefinitionType.GetGenericArguments()[0]; valueType = _genericCollectionDefinitionType.GetGenericArguments()[1]; + + if (ReflectionUtils.IsGenericDefinition(UnderlyingType, typeof(IDictionary<,>))) + CreatedType = ReflectionUtils.MakeGenericType(typeof(Dictionary<,>), keyType, valueType); } #if !(NET40 || NET35 || NET20 || SILVERLIGHT || WINDOWS_PHONE || PORTABLE) else if (ReflectionUtils.ImplementsGenericDefinition(underlyingType, typeof(IReadOnlyDictionary<,>), out _genericCollectionDefinitionType)) @@ -94,16 +97,22 @@ namespace Newtonsoft.Json.Serialization keyType = _genericCollectionDefinitionType.GetGenericArguments()[0]; valueType = _genericCollectionDefinitionType.GetGenericArguments()[1]; + if (ReflectionUtils.IsGenericDefinition(UnderlyingType, typeof(IReadOnlyDictionary<,>))) + CreatedType = ReflectionUtils.MakeGenericType(typeof(ReadOnlyDictionary<,>), keyType, valueType); + IsReadOnlyOrFixedSize = true; } #endif else { ReflectionUtils.GetDictionaryKeyValueTypes(UnderlyingType, out keyType, out valueType); + + if (UnderlyingType == typeof(IDictionary)) + CreatedType = typeof(Dictionary); } if (keyType != null && valueType != null) - ParametrizedConstructor = CollectionUtils.ResolveEnumableCollectionConstructor(underlyingType, ReflectionUtils.MakeGenericType(typeof(KeyValuePair<,>), keyType, valueType)); + ParametrizedConstructor = CollectionUtils.ResolveEnumableCollectionConstructor(CreatedType, ReflectionUtils.MakeGenericType(typeof(KeyValuePair<,>), keyType, valueType)); ShouldCreateWrapper = !typeof (IDictionary).IsAssignableFrom(underlyingType); @@ -112,21 +121,6 @@ namespace Newtonsoft.Json.Serialization if (DictionaryValueType != null) _isDictionaryValueTypeNullableType = ReflectionUtils.IsNullableType(DictionaryValueType); - - if (IsTypeGenericDictionaryInterface(UnderlyingType)) - { - CreatedType = ReflectionUtils.MakeGenericType(typeof(Dictionary<,>), keyType, valueType); - } -#if !(NET40 || NET35 || NET20 || SILVERLIGHT || WINDOWS_PHONE || PORTABLE) - else if (IsTypeGenericReadOnlyDictionaryInterface(UnderlyingType)) - { - CreatedType = ReflectionUtils.MakeGenericType(typeof(ReadOnlyDictionary<,>), keyType, valueType); - } -#endif - else if (UnderlyingType == typeof(IDictionary)) - { - CreatedType = typeof (Dictionary); - } } internal IWrappedDictionary CreateWrapper(object dictionary) @@ -156,27 +150,5 @@ namespace Newtonsoft.Json.Serialization return (IDictionary)_genericTemporaryDictionaryCreator(); } - - private bool IsTypeGenericDictionaryInterface(Type type) - { - if (!type.IsGenericType()) - return false; - - Type genericDefinition = type.GetGenericTypeDefinition(); - - return (genericDefinition == typeof(IDictionary<,>)); - } - -#if !(NET40 || NET35 || NET20 || SILVERLIGHT || WINDOWS_PHONE || PORTABLE) - private bool IsTypeGenericReadOnlyDictionaryInterface(Type type) - { - if (!type.IsGenericType()) - return false; - - Type genericDefinition = type.GetGenericTypeDefinition(); - - return (genericDefinition == typeof(IReadOnlyDictionary<,>)); - } -#endif } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs index d4967083..e06a220f 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs @@ -875,7 +875,12 @@ To fix this error either change the JSON to a {1} or change the deserialized typ if (contract.IsReadOnlyOrFixedSize) { createdFromNonDefaultConstructor = true; - return contract.CreateTemporaryCollection(); + IList list = contract.CreateTemporaryCollection(); + + if (contract.ShouldCreateWrapper) + list = contract.CreateWrapper(list); + + return list; } else if (contract.DefaultCreator != null && (!contract.DefaultCreatorNonPublic || Serializer.ConstructorHandling == ConstructorHandling.AllowNonPublicDefaultConstructor)) { diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs index c246f3b9..df6291b7 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalWriter.cs @@ -139,7 +139,7 @@ namespace Newtonsoft.Json.Serialization case JsonContractType.Array: JsonArrayContract arrayContract = (JsonArrayContract) valueContract; if (!arrayContract.IsMultidimensionalArray) - SerializeList(writer, (value is IList) ? (IList)value : arrayContract.CreateWrapper(value), arrayContract, member, containerContract, containerProperty); + SerializeList(writer, (IEnumerable)value, arrayContract, member, containerContract, containerProperty); else SerializeMultidimensionalArray(writer, (Array)value, arrayContract, member, containerContract, containerProperty); break; @@ -151,7 +151,7 @@ namespace Newtonsoft.Json.Serialization break; case JsonContractType.Dictionary: JsonDictionaryContract dictionaryContract = (JsonDictionaryContract) valueContract; - SerializeDictionary(writer, dictionaryContract.CreateWrapper(value), dictionaryContract, member, containerContract, containerProperty); + SerializeDictionary(writer, (value is IDictionary) ? (IDictionary) value : dictionaryContract.CreateWrapper(value), dictionaryContract, member, containerContract, containerProperty); break; #if !(NET35 || NET20) case JsonContractType.Dynamic: @@ -520,7 +520,7 @@ namespace Newtonsoft.Json.Serialization } } - private void SerializeList(JsonWriter writer, IList values, JsonArrayContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty) + private void SerializeList(JsonWriter writer, IEnumerable values, JsonArrayContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty) { IWrappedCollection wrappedCollection = values as IWrappedCollection; object underlyingList = wrappedCollection != null ? wrappedCollection.UnderlyingCollection : values; diff --git a/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs b/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs index 1703cf67..0e00cc8e 100644 --- a/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs @@ -305,6 +305,15 @@ namespace Newtonsoft.Json.Utilities : t; } + public static bool IsGenericDefinition(Type type, Type genericInterfaceDefinition) + { + if (!type.IsGenericType()) + return false; + + Type t = type.GetGenericTypeDefinition(); + return (t == genericInterfaceDefinition); + } + public static bool ImplementsGenericDefinition(Type type, Type genericInterfaceDefinition) { Type implementingType;