diff --git a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Compact.csproj b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Compact.csproj index f3ed4cbd..5ed093f4 100644 --- a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Compact.csproj +++ b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Compact.csproj @@ -72,6 +72,7 @@ + @@ -121,6 +122,8 @@ + + diff --git a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Net20.csproj b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Net20.csproj index 3f4481b7..fc9537b0 100644 --- a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Net20.csproj +++ b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Net20.csproj @@ -72,6 +72,7 @@ + @@ -86,6 +87,8 @@ + + diff --git a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Silverlight.csproj b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Silverlight.csproj index d4fdb92d..2f18bb4c 100644 --- a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Silverlight.csproj +++ b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.Silverlight.csproj @@ -78,6 +78,7 @@ + @@ -127,6 +128,8 @@ + + diff --git a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj index 5c49108c..a5de0bab 100644 --- a/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj +++ b/Src/Newtonsoft.Json.Tests/Newtonsoft.Json.Tests.csproj @@ -109,6 +109,9 @@ + + + diff --git a/Src/Newtonsoft.Json.Tests/PerformanceTests.cs b/Src/Newtonsoft.Json.Tests/PerformanceTests.cs index 7bb208aa..0cccbdae 100644 --- a/Src/Newtonsoft.Json.Tests/PerformanceTests.cs +++ b/Src/Newtonsoft.Json.Tests/PerformanceTests.cs @@ -167,7 +167,7 @@ namespace Newtonsoft.Json.Tests timed.Stop(); - Console.WriteLine("Serialize method: {0}", method); + Console.WriteLine("Deserialize method: {0}", method); Console.WriteLine("{0} ms", timed.ElapsedMilliseconds); Console.WriteLine(value); Console.WriteLine(); diff --git a/Src/Newtonsoft.Json.Tests/Serialization/ConstructorHandlingTests.cs b/Src/Newtonsoft.Json.Tests/Serialization/ConstructorHandlingTests.cs new file mode 100644 index 00000000..321da26f --- /dev/null +++ b/Src/Newtonsoft.Json.Tests/Serialization/ConstructorHandlingTests.cs @@ -0,0 +1,58 @@ +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using NUnit.Framework; + +namespace Newtonsoft.Json.Tests.Serialization +{ + public class ConstructorHandlingTests : TestFixtureBase + { + [Test] + [ExpectedException(typeof(JsonSerializationException), ExpectedMessage = "Unable to find a constructor to use for type Newtonsoft.Json.Tests.Serialization.PrivateConstructorTestClass. A class should either have a default constructor or only one constructor with arguments.")] + public void FailWithPrivateConstructorAndDefault() + { + string json = @"{Name:""Name!""}"; + + JsonConvert.DeserializeObject(json); + } + + [Test] + public void SuccessWithPrivateConstructorAndAllowNonPublic() + { + string json = @"{Name:""Name!""}"; + + PrivateConstructorTestClass c = JsonConvert.DeserializeObject(json, + new JsonSerializerSettings + { + ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor + }); + Assert.IsNotNull(c); + Assert.AreEqual("Name!", c.Name); + } + + [Test] + [ExpectedException(typeof(TargetInvocationException))] + public void FailWithPrivateConstructorPlusParametizedAndDefault() + { + string json = @"{Name:""Name!""}"; + + PrivateConstructorWithPublicParametizedConstructorTestClass c = JsonConvert.DeserializeObject(json); + } + + [Test] + public void SuccessWithPrivateConstructorPlusParametizedAndAllowNonPublic() + { + string json = @"{Name:""Name!""}"; + + PrivateConstructorWithPublicParametizedConstructorTestClass c = JsonConvert.DeserializeObject(json, + new JsonSerializerSettings + { + ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor + }); + Assert.IsNotNull(c); + Assert.AreEqual("Name!", c.Name); + Assert.AreEqual(1, c.Age); + } + } +} diff --git a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs index ae50dffc..eaa03aa7 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs @@ -1756,7 +1756,7 @@ keyword such as type of business."" } [Test] - [ExpectedException(typeof(JsonSerializationException), ExpectedMessage = @"Unable to determine which constructor to use for type Newtonsoft.Json.Tests.TestObjects.Event. A class with no default constructor should have only one constructor with arguments.")] + [ExpectedException(typeof(JsonSerializationException), ExpectedMessage = @"Unable to find a constructor to use for type Newtonsoft.Json.Tests.TestObjects.Event. A class should either have a default constructor or only one constructor with arguments.")] public void FailWhenClassWithNoDefaultConstructorHasMultipleConstructorsWithArguments() { string json = @"{""sublocation"":""AlertEmailSender.Program.Main"",""userId"":0,""type"":0,""summary"":""Loading settings variables"",""details"":null,""stackTrace"":"" at System.Environment.GetStackTrace(Exception e, Boolean needFileInfo)\r\n at System.Environment.get_StackTrace()\r\n at mr.Logging.Event..ctor(String summary) in C:\\Projects\\MRUtils\\Logging\\Event.vb:line 71\r\n at AlertEmailSender.Program.Main(String[] args) in C:\\Projects\\AlertEmailSender\\AlertEmailSender\\Program.cs:line 25"",""tag"":null,""time"":""\/Date(1249591032026-0400)\/""}"; diff --git a/Src/Newtonsoft.Json.Tests/TestObjects/PrivateConstructorTestClass.cs b/Src/Newtonsoft.Json.Tests/TestObjects/PrivateConstructorTestClass.cs new file mode 100644 index 00000000..ad52d170 --- /dev/null +++ b/Src/Newtonsoft.Json.Tests/TestObjects/PrivateConstructorTestClass.cs @@ -0,0 +1,37 @@ +#region License +// Copyright (c) 2007 James Newton-King +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +#endregion + +namespace Newtonsoft.Json.Tests.TestObjects +{ + public class PrivateConstructorTestClass + { + public string Name { get; set; } + public int Age { get; set; } + + private PrivateConstructorTestClass() + { + } + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json.Tests/TestObjects/PrivateConstructorWithPublicParametizedConstructorTestClass.cs b/Src/Newtonsoft.Json.Tests/TestObjects/PrivateConstructorWithPublicParametizedConstructorTestClass.cs new file mode 100644 index 00000000..8572ec64 --- /dev/null +++ b/Src/Newtonsoft.Json.Tests/TestObjects/PrivateConstructorWithPublicParametizedConstructorTestClass.cs @@ -0,0 +1,45 @@ +#region License +// Copyright (c) 2007 James Newton-King +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +#endregion + +using System; + +namespace Newtonsoft.Json.Tests.TestObjects +{ + public class PrivateConstructorWithPublicParametizedConstructorTestClass + { + public string Name { get; set; } + public int Age { get; set; } + + private PrivateConstructorWithPublicParametizedConstructorTestClass() + { + Age = 1; + } + + public PrivateConstructorWithPublicParametizedConstructorTestClass(string dummy) + { + throw new Exception("Should never get here."); + } + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/ConstructorHandling.cs b/Src/Newtonsoft.Json/ConstructorHandling.cs new file mode 100644 index 00000000..591d1508 --- /dev/null +++ b/Src/Newtonsoft.Json/ConstructorHandling.cs @@ -0,0 +1,47 @@ +#region License +// Copyright (c) 2007 James Newton-King +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Newtonsoft.Json +{ + /// + /// Specifies how constructors are used when initializing objects during deserialization by the . + /// + public enum ConstructorHandling + { + /// + /// First attempt to use the public default constructor then fall back to single paramatized constructor. + /// + Default = 0, + /// + /// Allow Json.NET to use a non-public default constructor. + /// + AllowNonPublicDefaultConstructor = 1 + } +} \ No newline at end of file diff --git a/Src/Newtonsoft.Json/JsonSerializer.cs b/Src/Newtonsoft.Json/JsonSerializer.cs index 9084247d..f3babd7d 100644 --- a/Src/Newtonsoft.Json/JsonSerializer.cs +++ b/Src/Newtonsoft.Json/JsonSerializer.cs @@ -47,6 +47,7 @@ namespace Newtonsoft.Json private ObjectCreationHandling _objectCreationHandling; private NullValueHandling _nullValueHandling; private DefaultValueHandling _defaultValueHandling; + private ConstructorHandling _constructorHandling; private JsonConverterCollection _converters; private IContractResolver _contractResolver; private IReferenceResolver _referenceResolver; @@ -204,6 +205,22 @@ namespace Newtonsoft.Json } } + /// + /// Gets or sets how constructors are used during deserialization. + /// + /// The constructor handling. + public virtual ConstructorHandling ConstructorHandling + { + get { return _constructorHandling; } + set + { + if (value < ConstructorHandling.Default || value > ConstructorHandling.AllowNonPublicDefaultConstructor) + throw new ArgumentOutOfRangeException("value"); + + _constructorHandling = value; + } + } + /// /// Gets a collection that will be used during serialization. /// @@ -247,6 +264,7 @@ namespace Newtonsoft.Json _defaultValueHandling = JsonSerializerSettings.DefaultDefaultValueHandling; _objectCreationHandling = JsonSerializerSettings.DefaultObjectCreationHandling; _preserveReferencesHandling = JsonSerializerSettings.DefaultPreserveReferencesHandling; + _constructorHandling = JsonSerializerSettings.DefaultConstructorHandling; _typeNameHandling = JsonSerializerSettings.DefaultTypeNameHandling; _binder = DefaultSerializationBinder.Instance; } @@ -272,6 +290,7 @@ namespace Newtonsoft.Json jsonSerializer.ObjectCreationHandling = settings.ObjectCreationHandling; jsonSerializer.NullValueHandling = settings.NullValueHandling; jsonSerializer.DefaultValueHandling = settings.DefaultValueHandling; + jsonSerializer.ConstructorHandling = settings.ConstructorHandling; if (settings.ContractResolver != null) jsonSerializer.ContractResolver = settings.ContractResolver; diff --git a/Src/Newtonsoft.Json/JsonSerializerSettings.cs b/Src/Newtonsoft.Json/JsonSerializerSettings.cs index 03d49ecb..f247ba0f 100644 --- a/Src/Newtonsoft.Json/JsonSerializerSettings.cs +++ b/Src/Newtonsoft.Json/JsonSerializerSettings.cs @@ -19,6 +19,7 @@ namespace Newtonsoft.Json internal const DefaultValueHandling DefaultDefaultValueHandling = DefaultValueHandling.Include; internal const ObjectCreationHandling DefaultObjectCreationHandling = ObjectCreationHandling.Auto; internal const PreserveReferencesHandling DefaultPreserveReferencesHandling = PreserveReferencesHandling.None; + internal const ConstructorHandling DefaultConstructorHandling = ConstructorHandling.Default; internal const TypeNameHandling DefaultTypeNameHandling = TypeNameHandling.None; /// @@ -61,6 +62,11 @@ namespace Newtonsoft.Json /// /// The type name handling. public TypeNameHandling TypeNameHandling { get; set; } + /// + /// Gets or sets how constructors are used during deserialization. + /// + /// The constructor handling. + public ConstructorHandling ConstructorHandling { get; set; } /// /// Gets or sets the contract resolver used by the serializer when diff --git a/Src/Newtonsoft.Json/JsonTextReader.cs b/Src/Newtonsoft.Json/JsonTextReader.cs index 697cbad5..83d4e981 100644 --- a/Src/Newtonsoft.Json/JsonTextReader.cs +++ b/Src/Newtonsoft.Json/JsonTextReader.cs @@ -347,6 +347,10 @@ namespace Newtonsoft.Json SetStateBasedOnCurrent(); ClearCurrentChar(); return false; + case ' ': + // eat whitespace + ClearCurrentChar(); + break; default: if (char.IsWhiteSpace(_currentChar)) { @@ -379,6 +383,9 @@ namespace Newtonsoft.Json case ',': SetToken(JsonToken.Undefined); return true; + case ' ': + // eat + break; default: if (char.IsWhiteSpace(_currentChar)) { @@ -547,6 +554,9 @@ namespace Newtonsoft.Json case ')': SetToken(JsonToken.EndConstructor); return true; + case ' ': + // eat + break; default: if (char.IsWhiteSpace(_currentChar)) { @@ -571,7 +581,7 @@ namespace Newtonsoft.Json private bool EatWhitespace(bool oneOrMore) { bool whitespace = false; - while (char.IsWhiteSpace(_currentChar)) + while (_currentChar == ' ' || char.IsWhiteSpace(_currentChar)) { whitespace = true; MoveNext(); @@ -736,6 +746,8 @@ namespace Newtonsoft.Json if (CurrentState == State.Constructor || CurrentState == State.ConstructorStart) return true; break; + case ' ': + return true; default: if (char.IsWhiteSpace(_currentChar)) return true; diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Compact.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Compact.csproj index 7e4bb94c..765c1d0d 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Compact.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Compact.csproj @@ -57,6 +57,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Net20.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Net20.csproj index 9c118e63..160d3cdb 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Net20.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Net20.csproj @@ -77,6 +77,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.Silverlight.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.Silverlight.csproj index 711475c5..e811cb9b 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.Silverlight.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.Silverlight.csproj @@ -49,6 +49,7 @@ + diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj index 78f2b6e3..81ff65ee 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj @@ -90,6 +90,7 @@ + diff --git a/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs b/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs index cd29f0da..d8ada149 100644 --- a/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs +++ b/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs @@ -140,10 +140,22 @@ namespace Newtonsoft.Json.Serialization contract.MemberSerialization = JsonTypeReflector.GetObjectMemberSerialization(objectType); contract.Properties.AddRange(CreateProperties(contract)); + if (contract.DefaultContstructor == null || contract.DefaultContstructor.IsPrivate) + contract.ParametrizedConstructor = GetParametrizedConstructor(objectType); return contract; } + private ConstructorInfo GetParametrizedConstructor(Type objectType) + { + ConstructorInfo[] constructors = objectType.GetConstructors(BindingFlags.Public | BindingFlags.Instance); + + if (constructors.Length == 1) + return constructors[0]; + else + return null; + } + private void InitializeContract(JsonContract contract) { JsonContainerAttribute containerAttribute = JsonTypeReflector.GetJsonContainerAttribute(contract.UnderlyingType); @@ -161,6 +173,10 @@ namespace Newtonsoft.Json.Serialization } #endif + contract.DefaultContstructor = + ReflectionUtils.GetDefaultConstructor(contract.CreatedType, false) ?? + ReflectionUtils.GetDefaultConstructor(contract.CreatedType, true); + foreach (MethodInfo method in contract.UnderlyingType.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)) { Type prevAttributeType = null; diff --git a/Src/Newtonsoft.Json/Serialization/JsonArrayContract.cs b/Src/Newtonsoft.Json/Serialization/JsonArrayContract.cs index 96208634..abe216bc 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonArrayContract.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonArrayContract.cs @@ -25,7 +25,9 @@ using System; using System.Collections.Generic; +using System.Reflection; using Newtonsoft.Json.Utilities; +using System.Collections; namespace Newtonsoft.Json.Serialization { @@ -34,9 +36,13 @@ namespace Newtonsoft.Json.Serialization /// public class JsonArrayContract : JsonContract { - internal Type CollectionTypeToCreate { get; private set; } internal Type CollectionItemType { get; private set; } + private bool _isCollectionItemTypeNullableType; + private Type _genericCollectionDefinitionType; + private Type _genericWrapperType; + private ConstructorInfo _genericWrapperConstructor; + /// /// Initializes a new instance of the class. /// @@ -44,17 +50,40 @@ namespace Newtonsoft.Json.Serialization public JsonArrayContract(Type underlyingType) : base(underlyingType) { - CollectionItemType = ReflectionUtils.GetCollectionItemType(UnderlyingType); - - if (IsTypeGenericCollectionInterface(UnderlyingType)) + if (ReflectionUtils.ImplementsGenericDefinition(underlyingType, typeof(ICollection<>), out _genericCollectionDefinitionType)) { - Type itemType = ReflectionUtils.GetCollectionItemType(UnderlyingType); - CollectionTypeToCreate = ReflectionUtils.MakeGenericType(typeof(List<>), itemType); + CollectionItemType = _genericCollectionDefinitionType.GetGenericArguments()[0]; } else { - CollectionTypeToCreate = UnderlyingType; + CollectionItemType = ReflectionUtils.GetCollectionItemType(UnderlyingType); } + + if (CollectionItemType != null) + _isCollectionItemTypeNullableType = ReflectionUtils.IsNullableType(CollectionItemType); + + if (IsTypeGenericCollectionInterface(UnderlyingType)) + { + CreatedType = ReflectionUtils.MakeGenericType(typeof(List<>), CollectionItemType); + } + else + { + CreatedType = UnderlyingType; + } + } + + internal IWrappedCollection CreateWrapper(object list) + { + if (list is IList && (CollectionItemType == null || !_isCollectionItemTypeNullableType)) + return new CollectionWrapper((IList)list); + + if (_genericWrapperType == null) + { + _genericWrapperType = ReflectionUtils.MakeGenericType(typeof(CollectionWrapper<>), CollectionItemType); + _genericWrapperConstructor = _genericWrapperType.GetConstructor(new[] { _genericCollectionDefinitionType }); + } + + return (IWrappedCollection)_genericWrapperConstructor.Invoke(new[] { list }); } private bool IsTypeGenericCollectionInterface(Type type) diff --git a/Src/Newtonsoft.Json/Serialization/JsonContract.cs b/Src/Newtonsoft.Json/Serialization/JsonContract.cs index 2ab8c2b6..b7adcb8d 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonContract.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonContract.cs @@ -40,6 +40,13 @@ namespace Newtonsoft.Json.Serialization /// /// The underlying type for the contract. public Type UnderlyingType { get; private set; } + + /// + /// Gets or sets the type created during deserialization. + /// + /// The type created during deserialization. + public Type CreatedType { get; set; } + /// /// Gets or sets whether this type contract is serialized as a reference. /// @@ -72,6 +79,12 @@ namespace Newtonsoft.Json.Serialization public MethodInfo OnSerializing { get; set; } #endif + /// + /// Gets or sets the default contstructor used to create the object. + /// + /// The default contstructor. + public ConstructorInfo DefaultContstructor { get; set; } + /// /// Gets or sets the method called when an error is thrown during the serialization of the object. /// diff --git a/Src/Newtonsoft.Json/Serialization/JsonDictionaryContract.cs b/Src/Newtonsoft.Json/Serialization/JsonDictionaryContract.cs index b88aedcd..73766bbf 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonDictionaryContract.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonDictionaryContract.cs @@ -25,7 +25,9 @@ using System; using System.Collections.Generic; +using System.Reflection; using Newtonsoft.Json.Utilities; +using System.Collections; namespace Newtonsoft.Json.Serialization { @@ -34,10 +36,13 @@ namespace Newtonsoft.Json.Serialization /// public class JsonDictionaryContract : JsonContract { - internal Type DictionaryTypeToCreate { get; private set; } internal Type DictionaryKeyType { get; private set; } internal Type DictionaryValueType { get; private set; } + private Type _genericCollectionDefinitionType; + private Type _genericWrapperType; + private ConstructorInfo _genericWrapperConstructor; + /// /// Initializes a new instance of the class. /// @@ -47,21 +52,43 @@ namespace Newtonsoft.Json.Serialization { Type keyType; Type valueType; - ReflectionUtils.GetDictionaryKeyValueTypes(UnderlyingType, out keyType, out valueType); + if (ReflectionUtils.ImplementsGenericDefinition(underlyingType, typeof(IDictionary<,>), out _genericCollectionDefinitionType)) + { + keyType = _genericCollectionDefinitionType.GetGenericArguments()[0]; + valueType = _genericCollectionDefinitionType.GetGenericArguments()[1]; + } + else + { + ReflectionUtils.GetDictionaryKeyValueTypes(UnderlyingType, out keyType, out valueType); + } DictionaryKeyType = keyType; DictionaryValueType = valueType; if (IsTypeGenericDictionaryInterface(UnderlyingType)) { - DictionaryTypeToCreate = ReflectionUtils.MakeGenericType(typeof(Dictionary<,>), keyType, valueType); + CreatedType = ReflectionUtils.MakeGenericType(typeof(Dictionary<,>), keyType, valueType); } else { - DictionaryTypeToCreate = underlyingType; + CreatedType = underlyingType; } } + internal IWrappedDictionary CreateWrapper(object dictionary) + { + if (dictionary is IDictionary) + return new DictionaryWrapper((IDictionary)dictionary); + + if (_genericWrapperType == null) + { + _genericWrapperType = ReflectionUtils.MakeGenericType(typeof(DictionaryWrapper<,>), DictionaryKeyType, DictionaryValueType); + _genericWrapperConstructor = _genericWrapperType.GetConstructor(new[] { _genericCollectionDefinitionType }); + } + + return (IWrappedDictionary)_genericWrapperConstructor.Invoke(new[] { dictionary }); + } + private bool IsTypeGenericDictionaryInterface(Type type) { if (!type.IsGenericType) diff --git a/Src/Newtonsoft.Json/Serialization/JsonObjectContract.cs b/Src/Newtonsoft.Json/Serialization/JsonObjectContract.cs index 05b3895c..58256413 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonObjectContract.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonObjectContract.cs @@ -24,6 +24,7 @@ #endregion using System; +using System.Reflection; namespace Newtonsoft.Json.Serialization { @@ -44,6 +45,12 @@ namespace Newtonsoft.Json.Serialization /// The object's properties. public JsonPropertyCollection Properties { get; private set; } + /// + /// Gets or sets the parametrized constructor used to create the object. + /// + /// The parametrized constructor. + public ConstructorInfo ParametrizedConstructor { get; set; } + /// /// Initializes a new instance of the class. /// @@ -52,6 +59,7 @@ namespace Newtonsoft.Json.Serialization : base(underlyingType) { Properties = new JsonPropertyCollection(); + CreatedType = underlyingType; } } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs index 53cde677..92f72847 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs @@ -435,7 +435,7 @@ namespace Newtonsoft.Json.Serialization private object CreateAndPopulateDictionary(JsonReader reader, JsonDictionaryContract contract, string id) { - IWrappedDictionary dictionary = CollectionUtils.CreateDictionaryWrapper(Activator.CreateInstance(contract.DictionaryTypeToCreate)); + IWrappedDictionary dictionary = contract.CreateWrapper(contract.DefaultContstructor.Invoke(null)); PopulateDictionary(dictionary, reader, contract, id); @@ -488,7 +488,7 @@ namespace Newtonsoft.Json.Serialization private object CreateAndPopulateList(JsonReader reader, string reference, JsonArrayContract contract) { - return CollectionUtils.CreateAndPopulateList(contract.CollectionTypeToCreate, (l, isTemporaryListReference) => + return CollectionUtils.CreateAndPopulateList(contract.CreatedType, (l, isTemporaryListReference) => { if (reference != null && isTemporaryListReference) throw new JsonSerializationException("Cannot preserve reference to array or readonly list: {0}".FormatWith(CultureInfo.InvariantCulture, contract.UnderlyingType)); @@ -500,7 +500,7 @@ namespace Newtonsoft.Json.Serialization if (contract.OnError != null && isTemporaryListReference) throw new JsonSerializationException("Cannot call OnError on an array or readonly list: {0}".FormatWith(CultureInfo.InvariantCulture, contract.UnderlyingType)); - PopulateList(CollectionUtils.CreateCollectionWrapper(l), reader, reference, contract); + PopulateList(contract.CreateWrapper(l), reader, reference, contract); }); } @@ -556,30 +556,24 @@ namespace Newtonsoft.Json.Serialization if (contract.UnderlyingType.IsInterface || contract.UnderlyingType.IsAbstract) throw new JsonSerializationException("Could not create an instance of type {0}. Type is an interface or abstract class and cannot be instantated.".FormatWith(CultureInfo.InvariantCulture, contract.UnderlyingType)); - if (ReflectionUtils.HasDefaultConstructor(contract.UnderlyingType)) + if (contract.DefaultContstructor != null && + (contract.DefaultContstructor.IsPublic || _serializer.ConstructorHandling == ConstructorHandling.AllowNonPublicDefaultConstructor)) { - newObject = Activator.CreateInstance(contract.UnderlyingType); + newObject = contract.DefaultContstructor.Invoke(null); PopulateObject(newObject, reader, contract, id); return newObject; } - return CreateObjectFromNonDefaultConstructor(contract, reader); + return CreateObjectFromNonDefaultConstructor(reader, contract, id); } - private object CreateObjectFromNonDefaultConstructor(JsonObjectContract contract, JsonReader reader) + private object CreateObjectFromNonDefaultConstructor(JsonReader reader, JsonObjectContract contract, string id) { Type objectType = contract.UnderlyingType; - ConstructorInfo[] constructors = objectType.GetConstructors(BindingFlags.Public | BindingFlags.Instance); - - if (constructors.Length == 0) - throw new JsonSerializationException("Could not find a public constructor for type {0}.".FormatWith(CultureInfo.InvariantCulture, objectType)); - if (constructors.Length > 1) - throw new JsonSerializationException("Unable to determine which constructor to use for type {0}. A class with no default constructor should have only one constructor with arguments.".FormatWith(CultureInfo.InvariantCulture, objectType)); - - // object should have a single constructor - ConstructorInfo c = constructors[0]; + if (contract.ParametrizedConstructor == null) + throw new JsonSerializationException("Unable to find a constructor to use for type {0}. A class should either have a default constructor or only one constructor with arguments.".FormatWith(CultureInfo.InvariantCulture, objectType)); // create a dictionary to put retrieved values into IDictionary propertyValues = contract.Properties.Where(p => !p.Ignored).ToDictionary(kv => kv, kv => (object)null); @@ -621,7 +615,7 @@ namespace Newtonsoft.Json.Serialization } } while (!exit && reader.Read()); - IDictionary constructorParameters = c.GetParameters().ToDictionary(p => p, p => (object)null); + IDictionary constructorParameters = contract.ParametrizedConstructor.GetParameters().ToDictionary(p => p, p => (object)null); IDictionary remainingPropertyValues = new Dictionary(); foreach (KeyValuePair propertyValue in propertyValues) @@ -633,7 +627,11 @@ namespace Newtonsoft.Json.Serialization remainingPropertyValues.Add(propertyValue); } - object createdObject = ReflectionUtils.CreateInstance(objectType, constructorParameters.Values.ToArray()); + object createdObject = contract.ParametrizedConstructor.Invoke(constructorParameters.Values.ToArray()); + + if (id != null) + _serializer.ReferenceResolver.AddReference(id, createdObject); + contract.InvokeOnDeserializing(createdObject); // go through unused values and set the newly created object's properties diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerProxy.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerProxy.cs index 9fbf2604..64bf989a 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerProxy.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerProxy.cs @@ -94,6 +94,12 @@ namespace Newtonsoft.Json.Serialization set { _serializer.TypeNameHandling = value; } } + public override ConstructorHandling ConstructorHandling + { + get { return _serializer.ConstructorHandling; } + set { _serializer.ConstructorHandling = value; } + } + public override SerializationBinder Binder { get { return _serializer.Binder; } diff --git a/Src/Newtonsoft.Json/Utilities/CollectionUtils.cs b/Src/Newtonsoft.Json/Utilities/CollectionUtils.cs index 2025f2e9..a7beeee2 100644 --- a/Src/Newtonsoft.Json/Utilities/CollectionUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/CollectionUtils.cs @@ -334,17 +334,6 @@ namespace Newtonsoft.Json.Utilities return result; } - public static T[] CreateArray(IEnumerable enumerable) - { - ValidationUtils.ArgumentNotNull(enumerable, "enumerable"); - - if (enumerable is T[]) - return (T[])enumerable; - - List tempList = new List(enumerable); - return tempList.ToArray(); - } - public static IList CreateGenericList(Type listType) { ValidationUtils.ArgumentNotNull(listType, "listType"); diff --git a/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs b/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs index 38417e1a..08fbb099 100644 --- a/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/ReflectionUtils.cs @@ -55,13 +55,30 @@ namespace Newtonsoft.Json.Utilities } public static bool HasDefaultConstructor(Type t) + { + return HasDefaultConstructor(t, false); + } + + public static bool HasDefaultConstructor(Type t, bool nonPublic) { ValidationUtils.ArgumentNotNull(t, "t"); if (t.IsValueType) return true; - return (t.GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, new Type[0], null) != null); + return (GetDefaultConstructor(t, nonPublic) != null); + } + + public static ConstructorInfo GetDefaultConstructor(Type t) + { + return GetDefaultConstructor(t, false); + } + + public static ConstructorInfo GetDefaultConstructor(Type t, bool nonPublic) + { + BindingFlags accessModifier = (!nonPublic) ? BindingFlags.Public : BindingFlags.NonPublic; + + return t.GetConstructor(accessModifier | BindingFlags.Instance, null, new Type[0], null); } public static bool IsNullable(Type t) @@ -575,20 +592,11 @@ namespace Newtonsoft.Json.Utilities ValidationUtils.ArgumentNotNullOrEmpty(innerTypes, "innerTypes"); ValidationUtils.ArgumentNotNull(instanceCreator, "createInstance"); - Type specificType = MakeGenericType(genericTypeDefinition, CollectionUtils.CreateArray(innerTypes)); + Type specificType = MakeGenericType(genericTypeDefinition, innerTypes.ToArray()); return instanceCreator(specificType, args); } - static object CreateInstance(this Assembly a, string typeName, params object[] pars) - { - var t = a.GetType(typeName); - var c = t.GetConstructor(pars.Select(p => p.GetType()).ToArray()); - if (c == null) - return null; - return c.Invoke(pars); - } - public static bool IsCompatibleValue(object value, Type type) { if (value == null && IsNullable(type))