diff --git a/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs index d4ff6088..7fc45ec3 100644 --- a/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs +++ b/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs @@ -75,4 +75,4 @@ using System.Security; // by using the '*' as shown below: [assembly: AssemblyVersion("6.0.0.0")] -[assembly: AssemblyFileVersion("6.0.3.17302")] +[assembly: AssemblyFileVersion("6.0.3.17307")] diff --git a/Src/Newtonsoft.Json.Tests/Serialization/ConstructorHandlingTests.cs b/Src/Newtonsoft.Json.Tests/Serialization/ConstructorHandlingTests.cs index a4442356..8c0fcf77 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/ConstructorHandlingTests.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/ConstructorHandlingTests.cs @@ -23,6 +23,7 @@ // OTHER DEALINGS IN THE SOFTWARE. #endregion +using System; using System.Reflection; using Newtonsoft.Json.Tests.TestObjects; #if !NETFX_CORE @@ -66,7 +67,7 @@ namespace Newtonsoft.Json.Tests.Serialization [Test] public void FailWithPrivateConstructorPlusParametizedAndDefault() { - ExceptionAssert.Throws( + ExceptionAssert.Throws( null, () => { diff --git a/Src/Newtonsoft.Json.Tests/Serialization/ContractResolverTests.cs b/Src/Newtonsoft.Json.Tests/Serialization/ContractResolverTests.cs index 98716ed7..ce2e34ad 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/ContractResolverTests.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/ContractResolverTests.cs @@ -150,6 +150,74 @@ namespace Newtonsoft.Json.Tests.Serialization Assert.IsFalse(contract.Properties.Any(c => c.PropertyName == "InvariantInfo")); } + [Test] + public void ParametrizedCreator() + { + var resolver = new DefaultContractResolver(); + var contract = (JsonObjectContract)resolver.ResolveContract(typeof(PublicParametizedConstructorWithPropertyNameConflictWithAttribute)); + + Assert.IsNull(contract.DefaultCreator); + Assert.IsNotNull(contract.ParametrizedCreator); +#pragma warning disable 618 + Assert.AreEqual(contract.ParametrizedConstructor, typeof(PublicParametizedConstructorWithPropertyNameConflictWithAttribute).GetConstructor(new[] { typeof(string) })); +#pragma warning restore 618 + Assert.AreEqual(1, contract.CreatorParameters.Count); + Assert.AreEqual("name", contract.CreatorParameters[0].PropertyName); + +#pragma warning disable 618 + contract.ParametrizedConstructor = null; +#pragma warning restore 618 + Assert.IsNull(contract.ParametrizedCreator); + } + + [Test] + public void OverrideCreator() + { + var resolver = new DefaultContractResolver(); + var contract = (JsonObjectContract)resolver.ResolveContract(typeof(MultipleParamatrizedConstructorsJsonConstructor)); + + Assert.IsNull(contract.DefaultCreator); + Assert.IsNotNull(contract.OverrideCreator); +#pragma warning disable 618 + Assert.AreEqual(contract.OverrideConstructor, typeof(MultipleParamatrizedConstructorsJsonConstructor).GetConstructor(new[] { typeof(string), typeof(int) })); +#pragma warning restore 618 + Assert.AreEqual(2, contract.CreatorParameters.Count); + Assert.AreEqual("Value", contract.CreatorParameters[0].PropertyName); + Assert.AreEqual("Age", contract.CreatorParameters[1].PropertyName); + +#pragma warning disable 618 + contract.OverrideConstructor = null; +#pragma warning restore 618 + Assert.IsNull(contract.OverrideCreator); + } + + [Test] + public void CustomOverrideCreator() + { + var resolver = new DefaultContractResolver(); + var contract = (JsonObjectContract)resolver.ResolveContract(typeof(MultipleParamatrizedConstructorsJsonConstructor)); + + bool ensureCustomCreatorCalled = false; + + contract.OverrideCreator = args => + { + ensureCustomCreatorCalled = true; + return new MultipleParamatrizedConstructorsJsonConstructor((string) args[0], (int) args[1]); + }; +#pragma warning disable 618 + Assert.IsNull(contract.OverrideConstructor); +#pragma warning restore 618 + + var o = JsonConvert.DeserializeObject("{Value:'value!', Age:1}", new JsonSerializerSettings + { + ContractResolver = resolver + }); + + Assert.AreEqual("value!", o.Value); + Assert.AreEqual(1, o.Age); + Assert.IsTrue(ensureCustomCreatorCalled); + } + [Test] public void SerializeInterface() { diff --git a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs index b22f2b14..5c504b09 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs @@ -4952,27 +4952,6 @@ To fix this error either change the environment to be fully trusted, change the Assert.IsTrue(deserializedResponse.Data.DeepEquals(response.Data)); } - public abstract class Test - { - public abstract T Value { get; set; } - } - - [JsonObject(MemberSerialization.OptIn)] - public class DecimalTest : Test - { - protected DecimalTest() - { - } - - public DecimalTest(decimal val) - { - Value = val; - } - - [JsonProperty] - public override decimal Value { get; set; } - } - [Test] public void DeserializeMinValueDecimal() { @@ -4983,24 +4962,6 @@ To fix this error either change the environment to be fully trusted, change the Assert.AreEqual(decimal.MinValue, obj.Value); } - public class NonPublicConstructorWithJsonConstructor - { - public string Value { get; private set; } - public string Constructor { get; private set; } - - [JsonConstructor] - private NonPublicConstructorWithJsonConstructor() - { - Constructor = "NonPublic"; - } - - public NonPublicConstructorWithJsonConstructor(string value) - { - Value = value; - Constructor = "Public Paramatized"; - } - } - [Test] public void NonPublicConstructorWithJsonConstructorTest() { @@ -5008,24 +4969,6 @@ To fix this error either change the environment to be fully trusted, change the Assert.AreEqual("NonPublic", c.Constructor); } - public class PublicConstructorOverridenByJsonConstructor - { - public string Value { get; private set; } - public string Constructor { get; private set; } - - public PublicConstructorOverridenByJsonConstructor() - { - Constructor = "NonPublic"; - } - - [JsonConstructor] - public PublicConstructorOverridenByJsonConstructor(string value) - { - Value = value; - Constructor = "Public Paramatized"; - } - } - [Test] public void PublicConstructorOverridenByJsonConstructorTest() { @@ -5034,27 +4977,6 @@ To fix this error either change the environment to be fully trusted, change the Assert.AreEqual("value!", c.Value); } - public class MultipleParamatrizedConstructorsJsonConstructor - { - public string Value { get; private set; } - public int Age { get; private set; } - public string Constructor { get; private set; } - - public MultipleParamatrizedConstructorsJsonConstructor(string value) - { - Value = value; - Constructor = "Public Paramatized 1"; - } - - [JsonConstructor] - public MultipleParamatrizedConstructorsJsonConstructor(string value, int age) - { - Value = value; - Age = age; - Constructor = "Public Paramatized 2"; - } - } - [Test] public void MultipleParamatrizedConstructorsJsonConstructorTest() { @@ -5064,11 +4986,6 @@ To fix this error either change the environment to be fully trusted, change the Assert.AreEqual(1, c.Age); } - public class EnumerableClass - { - public IEnumerable Enumerable { get; set; } - } - [Test] public void DeserializeEnumerable() { @@ -5094,18 +5011,6 @@ To fix this error either change the environment to be fully trusted, change the Assert.AreEqual("Three", c2.Enumerable.ElementAt(2)); } - [JsonObject(MemberSerialization.OptIn)] - public class ItemBase - { - [JsonProperty] - public string Name { get; set; } - } - - public class ComplexItem : ItemBase - { - public Stream Source { get; set; } - } - [Test] public void SerializeAttributesOnBase() { @@ -5118,14 +5023,6 @@ To fix this error either change the environment to be fully trusted, change the }", json); } - public class DeserializeStringConvert - { - public string Name { get; set; } - public int Age { get; set; } - public double Height { get; set; } - public decimal Price { get; set; } - } - [Test] public void DeserializeStringEnglish() { @@ -7961,4 +7858,107 @@ Parameter name: value", } #endif } + + public abstract class Test + { + public abstract T Value { get; set; } + } + + [JsonObject(MemberSerialization.OptIn)] + public class DecimalTest : Test + { + protected DecimalTest() + { + } + + public DecimalTest(decimal val) + { + Value = val; + } + + [JsonProperty] + public override decimal Value { get; set; } + } + + public class NonPublicConstructorWithJsonConstructor + { + public string Value { get; private set; } + public string Constructor { get; private set; } + + [JsonConstructor] + private NonPublicConstructorWithJsonConstructor() + { + Constructor = "NonPublic"; + } + + public NonPublicConstructorWithJsonConstructor(string value) + { + Value = value; + Constructor = "Public Paramatized"; + } + } + + public class PublicConstructorOverridenByJsonConstructor + { + public string Value { get; private set; } + public string Constructor { get; private set; } + + public PublicConstructorOverridenByJsonConstructor() + { + Constructor = "NonPublic"; + } + + [JsonConstructor] + public PublicConstructorOverridenByJsonConstructor(string value) + { + Value = value; + Constructor = "Public Paramatized"; + } + } + + public class MultipleParamatrizedConstructorsJsonConstructor + { + public string Value { get; private set; } + public int Age { get; private set; } + public string Constructor { get; private set; } + + public MultipleParamatrizedConstructorsJsonConstructor(string value) + { + Value = value; + Constructor = "Public Paramatized 1"; + } + + [JsonConstructor] + public MultipleParamatrizedConstructorsJsonConstructor(string value, int age) + { + Value = value; + Age = age; + Constructor = "Public Paramatized 2"; + } + } + + public class EnumerableClass + { + public IEnumerable Enumerable { get; set; } + } + + [JsonObject(MemberSerialization.OptIn)] + public class ItemBase + { + [JsonProperty] + public string Name { get; set; } + } + + public class ComplexItem : ItemBase + { + public Stream Source { get; set; } + } + + public class DeserializeStringConvert + { + public string Name { get; set; } + public int Age { get; set; } + public double Height { get; set; } + public decimal Price { get; set; } + } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json.Tests/Serialization/TraceWriterTests.cs b/Src/Newtonsoft.Json.Tests/Serialization/TraceWriterTests.cs index f8bf157f..1d4bc2ed 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/TraceWriterTests.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/TraceWriterTests.cs @@ -279,7 +279,7 @@ Newtonsoft.Json Error: 0 : Error! Assert.IsTrue(traceWriter.TraceRecords[2].Message.StartsWith("Finished deserializing System.Collections.Generic.IList`1[System.Int32]. Path 'IntList'")); Assert.AreEqual("Started deserializing System.String[]. Path 'StringArray', line 6, position 19.", traceWriter.TraceRecords[3].Message); Assert.IsTrue(traceWriter.TraceRecords[4].Message.StartsWith("Finished deserializing System.String[]. Path 'StringArray'")); - Assert.AreEqual("Deserializing System.Version using a non-default constructor 'Void .ctor(Int32, Int32, Int32, Int32)'. Path 'Version.Major', line 11, position 13.", traceWriter.TraceRecords[5].Message); + Assert.AreEqual("Deserializing System.Version using creator with parameters: Major, Minor, Build, Revision. Path 'Version.Major', line 11, position 13.", traceWriter.TraceRecords[5].Message); Assert.IsTrue(traceWriter.TraceRecords[6].Message.StartsWith("Started deserializing System.Version. Path 'Version'")); Assert.IsTrue(traceWriter.TraceRecords[7].Message.StartsWith("Finished deserializing System.Version. Path 'Version'")); Assert.AreEqual("Started deserializing System.Collections.Generic.IDictionary`2[System.String,System.String]. Path 'StringDictionary.1', line 19, position 9.", traceWriter.TraceRecords[8].Message); @@ -533,7 +533,7 @@ Newtonsoft.Json Error: 0 : Error! Assert.AreEqual("Started deserializing System.Collections.Generic.Dictionary`2[System.String,System.String]. Path '$values[0].key!', line 6, position 14.", traceWriter.TraceRecords[3].Message); Assert.IsTrue(traceWriter.TraceRecords[4].Message.StartsWith("Finished deserializing System.Collections.Generic.Dictionary`2[System.String,System.String]. Path '$values[0]'")); Assert.AreEqual("Resolved type 'System.Version, mscorlib' to System.Version. Path '$values[1].$type', line 9, position 42.", traceWriter.TraceRecords[5].Message); - Assert.AreEqual("Deserializing System.Version using a non-default constructor 'Void .ctor(Int32, Int32, Int32, Int32)'. Path '$values[1].Major', line 10, position 15.", traceWriter.TraceRecords[6].Message); + Assert.AreEqual("Deserializing System.Version using creator with parameters: Major, Minor, Build, Revision. Path '$values[1].Major', line 10, position 15.", traceWriter.TraceRecords[6].Message); Assert.IsTrue(traceWriter.TraceRecords[7].Message.StartsWith("Started deserializing System.Version. Path '$values[1]'")); Assert.IsTrue(traceWriter.TraceRecords[8].Message.StartsWith("Finished deserializing System.Version. Path '$values[1]'")); Assert.IsTrue(traceWriter.TraceRecords[9].Message.StartsWith("Finished deserializing System.Collections.Generic.List`1[System.Object]. Path '$values'")); @@ -610,12 +610,33 @@ Newtonsoft.Json Error: 0 : Error! TraceWriter = traceWriter }); - Assert.AreEqual("Deserializing System.Version using a non-default constructor 'Void .ctor(Int32, Int32, Int32, Int32)'. Path 'Major', line 2, position 11.", traceWriter.TraceRecords[0].Message); + Assert.AreEqual("Deserializing System.Version using creator with parameters: Major, Minor, Build, Revision. Path 'Major', line 2, position 11.", traceWriter.TraceRecords[0].Message); Assert.AreEqual("Could not find member 'MissingMemberProperty' on System.Version. Path 'MissingMemberProperty', line 8, position 32.", traceWriter.TraceRecords[1].Message); Assert.IsTrue(traceWriter.TraceRecords[2].Message.StartsWith("Started deserializing System.Version. Path ''")); Assert.IsTrue(traceWriter.TraceRecords[3].Message.StartsWith("Finished deserializing System.Version. Path ''")); } + [Test] + public void PublicParametizedConstructorWithPropertyNameConflictWithAttribute() + { + InMemoryTraceWriter traceWriter = new InMemoryTraceWriter + { + LevelFilter = TraceLevel.Verbose + }; + + string json = @"{name:""1""}"; + + PublicParametizedConstructorWithPropertyNameConflictWithAttribute c = JsonConvert.DeserializeObject(json, new JsonSerializerSettings + { + TraceWriter = traceWriter + }); + + Assert.IsNotNull(c); + Assert.AreEqual(1, c.Name); + + Assert.AreEqual("Deserializing Newtonsoft.Json.Tests.TestObjects.PublicParametizedConstructorWithPropertyNameConflictWithAttribute using creator with parameters: name. Path 'name', line 1, position 6.", traceWriter.TraceRecords[0].Message); + } + [Test] public void ShouldSerializeTestClass() { diff --git a/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs index 286e5c28..eb7463eb 100644 --- a/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs +++ b/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs @@ -91,5 +91,5 @@ using System.Security; // by using the '*' as shown below: [assembly: AssemblyVersion("6.0.0.0")] -[assembly: AssemblyFileVersion("6.0.3.17302")] +[assembly: AssemblyFileVersion("6.0.3.17307")] [assembly: CLSCompliant(true)] diff --git a/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs b/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs index 011a4383..5fa05f5c 100644 --- a/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs +++ b/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs @@ -380,8 +380,10 @@ namespace Newtonsoft.Json.Serialization // check if a JsonConstructorAttribute has been defined and use that if (overrideConstructor != null) { +#pragma warning disable 618 contract.OverrideConstructor = overrideConstructor; - contract.ConstructorParameters.AddRange(CreateConstructorParameters(overrideConstructor, contract.Properties)); +#pragma warning restore 618 + contract.CreatorParameters.AddRange(CreateConstructorParameters(overrideConstructor, contract.Properties)); } else if (contract.MemberSerialization == MemberSerialization.Fields) { @@ -397,8 +399,10 @@ namespace Newtonsoft.Json.Serialization ConstructorInfo constructor = GetParametrizedConstructor(contract.NonNullableUnderlyingType); if (constructor != null) { +#pragma warning disable 618 contract.ParametrizedConstructor = constructor; - contract.ConstructorParameters.AddRange(CreateConstructorParameters(constructor, contract.Properties)); +#pragma warning restore 618 + contract.CreatorParameters.AddRange(CreateConstructorParameters(constructor, contract.Properties)); } } diff --git a/Src/Newtonsoft.Json/Serialization/JsonObjectContract.cs b/Src/Newtonsoft.Json/Serialization/JsonObjectContract.cs index 11e11e80..6b13787f 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonObjectContract.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonObjectContract.cs @@ -60,7 +60,16 @@ namespace Newtonsoft.Json.Serialization /// /// Gets the constructor parameters required for any non-default constructor /// - public JsonPropertyCollection ConstructorParameters { get; private set; } + [Obsolete("ConstructorParameters is obsolete. Use CreatorParameters instead.")] + public JsonPropertyCollection ConstructorParameters + { + get { return CreatorParameters; } + } + + /// + /// Gets a collection of instances that define the parameters used with . + /// + public JsonPropertyCollection CreatorParameters { get; private set; } /// /// Gets or sets the override constructor used to create the object. @@ -68,13 +77,68 @@ namespace Newtonsoft.Json.Serialization /// JsonConstructor attribute. /// /// The override constructor. - public ConstructorInfo OverrideConstructor { get; set; } + [Obsolete("OverrideConstructor is obsolete. Use OverrideCreator instead.")] + public ConstructorInfo OverrideConstructor + { + get { return _overrideConstructor; } + set + { + _overrideConstructor = value; + + if (value != null) + { + MethodCall ctor = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall(value); + _overrideCreator = args => ctor(null, args); + } + else + { + _overrideCreator = null; + } + } + } /// /// Gets or sets the parametrized constructor used to create the object. /// /// The parametrized constructor. - public ConstructorInfo ParametrizedConstructor { get; set; } + [Obsolete("ParametrizedConstructor is obsolete. Use OverrideCreator instead.")] + public ConstructorInfo ParametrizedConstructor + { + get { return _parametrizedConstructor; } + set + { + _parametrizedConstructor = value; + if (value != null) + { + MethodCall ctor = JsonTypeReflector.ReflectionDelegateFactory.CreateMethodCall(value); + _parametrizedCreator = args => ctor(null, args); + } + else + { + _parametrizedCreator = null; + } + } + } + + /// + /// Gets or sets the function used to create the object. When set this function will override . + /// This function can be called with a collection of arguments which are defined by the collection. + /// + /// The function used to create the object. + public Func OverrideCreator + { + get { return _overrideCreator; } + set + { + _overrideCreator = value; + _overrideConstructor = null; + } + } + + internal Func ParametrizedCreator + { + get { return _parametrizedCreator; } + } /// /// Gets or sets the extension data setter. @@ -87,6 +151,10 @@ namespace Newtonsoft.Json.Serialization public ExtensionDataGetter ExtensionDataGetter { get; set; } private bool? _hasRequiredOrDefaultValueProperties; + private ConstructorInfo _parametrizedConstructor; + private ConstructorInfo _overrideConstructor; + private Func _overrideCreator; + private Func _parametrizedCreator; internal bool HasRequiredOrDefaultValueProperties { @@ -127,7 +195,7 @@ namespace Newtonsoft.Json.Serialization ContractType = JsonContractType.Object; Properties = new JsonPropertyCollection(UnderlyingType); - ConstructorParameters = new JsonPropertyCollection(UnderlyingType); + CreatorParameters = new JsonPropertyCollection(UnderlyingType); } #if !(NETFX_CORE || PORTABLE40 || PORTABLE) diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs index 5d025cdb..4ae60ed2 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs @@ -407,17 +407,17 @@ namespace Newtonsoft.Json.Serialization { case JsonContractType.Object: { - bool createdFromNonDefaultConstructor = false; + bool createdFromNonDefaultCreator = false; JsonObjectContract objectContract = (JsonObjectContract)contract; object targetObject; // check that if type name handling is being used that the existing value is compatible with the specified type if (existingValue != null && (resolvedObjectType == objectType || resolvedObjectType.IsAssignableFrom(existingValue.GetType()))) targetObject = existingValue; else - targetObject = CreateNewObject(reader, objectContract, member, containerMember, id, out createdFromNonDefaultConstructor); + targetObject = CreateNewObject(reader, objectContract, member, containerMember, id, out createdFromNonDefaultCreator); - // don't populate if read from non-default constructor because the object has already been read - if (createdFromNonDefaultConstructor) + // don't populate if read from non-default creator because the object has already been read + if (createdFromNonDefaultCreator) return targetObject; return PopulateObject(targetObject, reader, objectContract, member, id); @@ -449,10 +449,10 @@ namespace Newtonsoft.Json.Serialization if (existingValue == null) { - bool createdFromNonDefaultConstructor; - IDictionary dictionary = CreateNewDictionary(reader, dictionaryContract, out createdFromNonDefaultConstructor); + bool createdFromNonDefaultCreator; + IDictionary dictionary = CreateNewDictionary(reader, dictionaryContract, out createdFromNonDefaultCreator); - if (createdFromNonDefaultConstructor) + if (createdFromNonDefaultCreator) { if (id != null) throw JsonSerializationException.Create(reader, "Cannot preserve reference to readonly dictionary, or dictionary created from a non-default constructor: {0}.".FormatWith(CultureInfo.InvariantCulture, contract.UnderlyingType)); @@ -469,7 +469,7 @@ namespace Newtonsoft.Json.Serialization PopulateDictionary(dictionary, reader, dictionaryContract, member, id); - if (createdFromNonDefaultConstructor) + if (createdFromNonDefaultCreator) { return dictionaryContract.ParametrizedCreator(null, new object[] { dictionary }); } @@ -745,10 +745,10 @@ To fix this error either change the JSON to a {1} or change the deserialized typ if (existingValue == null) { - bool createdFromNonDefaultConstructor; - IList list = CreateNewList(reader, arrayContract, out createdFromNonDefaultConstructor); + bool createdFromNonDefaultCreator; + IList list = CreateNewList(reader, arrayContract, out createdFromNonDefaultCreator); - if (createdFromNonDefaultConstructor) + if (createdFromNonDefaultCreator) { if (id != null) throw JsonSerializationException.Create(reader, "Cannot preserve reference to array or readonly list, or list created from a non-default constructor: {0}.".FormatWith(CultureInfo.InvariantCulture, contract.UnderlyingType)); @@ -768,7 +768,7 @@ To fix this error either change the JSON to a {1} or change the deserialized typ else PopulateMultidimensionalArray(list, reader, arrayContract, member, id); - if (createdFromNonDefaultConstructor) + if (createdFromNonDefaultCreator) { if (arrayContract.IsMultidimensionalArray) { @@ -1003,7 +1003,7 @@ To fix this error either change the JSON to a {1} or change the deserialized typ return true; } - private IList CreateNewList(JsonReader reader, JsonArrayContract contract, out bool createdFromNonDefaultConstructor) + private IList CreateNewList(JsonReader reader, JsonArrayContract contract, out bool createdFromNonDefaultCreator) { // some types like non-generic IEnumerable can be serialized but not deserialized if (!contract.CanDeserialize) @@ -1011,7 +1011,7 @@ To fix this error either change the JSON to a {1} or change the deserialized typ if (contract.IsReadOnlyOrFixedSize) { - createdFromNonDefaultConstructor = true; + createdFromNonDefaultCreator = true; IList list = contract.CreateTemporaryCollection(); if (contract.ShouldCreateWrapper) @@ -1026,12 +1026,12 @@ To fix this error either change the JSON to a {1} or change the deserialized typ if (contract.ShouldCreateWrapper) list = contract.CreateWrapper(list); - createdFromNonDefaultConstructor = false; + createdFromNonDefaultCreator = false; return (IList)list; } else if (contract.ParametrizedCreator != null) { - createdFromNonDefaultConstructor = true; + createdFromNonDefaultCreator = true; return contract.CreateTemporaryCollection(); } else @@ -1040,11 +1040,11 @@ To fix this error either change the JSON to a {1} or change the deserialized typ } } - private IDictionary CreateNewDictionary(JsonReader reader, JsonDictionaryContract contract, out bool createdFromNonDefaultConstructor) + private IDictionary CreateNewDictionary(JsonReader reader, JsonDictionaryContract contract, out bool createdFromNonDefaultCreator) { if (contract.IsReadOnlyOrFixedSize) { - createdFromNonDefaultConstructor = true; + createdFromNonDefaultCreator = true; return contract.CreateTemporaryDictionary(); } else if (contract.DefaultCreator != null && (!contract.DefaultCreatorNonPublic || Serializer._constructorHandling == ConstructorHandling.AllowNonPublicDefaultConstructor)) @@ -1054,12 +1054,12 @@ To fix this error either change the JSON to a {1} or change the deserialized typ if (contract.ShouldCreateWrapper) dictionary = contract.CreateWrapper(dictionary); - createdFromNonDefaultConstructor = false; + createdFromNonDefaultCreator = false; return (IDictionary)dictionary; } else if (contract.ParametrizedCreator != null) { - createdFromNonDefaultConstructor = true; + createdFromNonDefaultCreator = true; return contract.CreateTemporaryDictionary(); } else @@ -1568,9 +1568,9 @@ To fix this error either change the environment to be fully trusted, change the } #endif - private object CreateObjectFromNonDefaultConstructor(JsonReader reader, JsonObjectContract contract, JsonProperty containerProperty, ConstructorInfo constructorInfo, string id) + private object CreateObjectUsingCreatorWithParameters(JsonReader reader, JsonObjectContract contract, JsonProperty containerProperty, Func creator, string id) { - ValidationUtils.ArgumentNotNull(constructorInfo, "constructorInfo"); + ValidationUtils.ArgumentNotNull(creator, "creator"); // only need to keep a track of properies presence if they are required or a value should be defaulted if missing Dictionary propertiesPresence = (contract.HasRequiredOrDefaultValueProperties || HasFlag(Serializer._defaultValueHandling, DefaultValueHandling.Populate)) @@ -1580,32 +1580,35 @@ To fix this error either change the environment to be fully trusted, change the Type objectType = contract.UnderlyingType; if (TraceWriter != null && TraceWriter.LevelFilter >= TraceLevel.Info) - TraceWriter.Trace(TraceLevel.Info, JsonPosition.FormatMessage(reader as IJsonLineInfo, reader.Path, "Deserializing {0} using a non-default constructor '{1}'.".FormatWith(CultureInfo.InvariantCulture, contract.UnderlyingType, constructorInfo)), null); + { + string parameters = string.Join(", ", contract.CreatorParameters.Select(p => p.PropertyName).ToArray()); + TraceWriter.Trace(TraceLevel.Info, JsonPosition.FormatMessage(reader as IJsonLineInfo, reader.Path, "Deserializing {0} using creator with parameters: {1}.".FormatWith(CultureInfo.InvariantCulture, contract.UnderlyingType, parameters)), null); + } IDictionary extensionData; - IDictionary propertyValues = ResolvePropertyAndConstructorValues(contract, containerProperty, reader, objectType, out extensionData); + IDictionary propertyValues = ResolvePropertyAndCreatorValues(contract, containerProperty, reader, objectType, out extensionData); - IDictionary constructorParameters = constructorInfo.GetParameters().ToDictionary(p => p, p => (object)null); + IDictionary creatorParameters = contract.CreatorParameters.ToDictionary(p => p, p => (object)null); IDictionary remainingPropertyValues = new Dictionary(); foreach (KeyValuePair propertyValue in propertyValues) { - ParameterInfo matchingConstructorParameter = constructorParameters.ForgivingCaseSensitiveFind(kv => kv.Key.Name, propertyValue.Key.UnderlyingName).Key; - if (matchingConstructorParameter != null) - constructorParameters[matchingConstructorParameter] = propertyValue.Value; + JsonProperty matchingCreatorParameter = creatorParameters.ForgivingCaseSensitiveFind(kv => kv.Key.PropertyName, propertyValue.Key.PropertyName).Key; + if (matchingCreatorParameter != null) + creatorParameters[matchingCreatorParameter] = propertyValue.Value; else remainingPropertyValues.Add(propertyValue); if (propertiesPresence != null) { - // map from constructor property to normal property + // map from creator property to normal property var property = propertiesPresence.Keys.FirstOrDefault(p => p.PropertyName == propertyValue.Key.PropertyName); if (property != null) propertiesPresence[property] = (propertyValue.Value == null) ? PropertyPresence.Null : PropertyPresence.Value; } } - object createdObject = constructorInfo.Invoke(constructorParameters.Values.ToArray()); + object createdObject = creator(creatorParameters.Values.ToArray()); if (id != null) AddReference(reader, id, createdObject); @@ -1689,7 +1692,7 @@ To fix this error either change the environment to be fully trusted, change the return value; } - private IDictionary ResolvePropertyAndConstructorValues(JsonObjectContract contract, JsonProperty containerProperty, JsonReader reader, Type objectType, out IDictionary extensionData) + private IDictionary ResolvePropertyAndCreatorValues(JsonObjectContract contract, JsonProperty containerProperty, JsonReader reader, Type objectType, out IDictionary extensionData) { extensionData = (contract.ExtensionDataSetter != null) ? new Dictionary() : null; @@ -1704,7 +1707,7 @@ To fix this error either change the environment to be fully trusted, change the // attempt exact case match first // then try match ignoring case - JsonProperty property = contract.ConstructorParameters.GetClosestMatchProperty(memberName) ?? + JsonProperty property = contract.CreatorParameters.GetClosestMatchProperty(memberName) ?? contract.Properties.GetClosestMatchProperty(memberName); if (property != null) @@ -1813,42 +1816,42 @@ To fix this error either change the environment to be fully trusted, change the return (reader.TokenType != JsonToken.None); } - public object CreateNewObject(JsonReader reader, JsonObjectContract objectContract, JsonProperty containerMember, JsonProperty containerProperty, string id, out bool createdFromNonDefaultConstructor) + public object CreateNewObject(JsonReader reader, JsonObjectContract objectContract, JsonProperty containerMember, JsonProperty containerProperty, string id, out bool createdFromNonDefaultCreator) { object newObject = null; if (!objectContract.IsInstantiable) throw JsonSerializationException.Create(reader, "Could not create an instance of type {0}. Type is an interface or abstract class and cannot be instantiated.".FormatWith(CultureInfo.InvariantCulture, objectContract.UnderlyingType)); - if (objectContract.OverrideConstructor != null) + if (objectContract.OverrideCreator != null) { - if (objectContract.OverrideConstructor.GetParameters().Length > 0) + if (objectContract.CreatorParameters.Count > 0) { - createdFromNonDefaultConstructor = true; - return CreateObjectFromNonDefaultConstructor(reader, objectContract, containerMember, objectContract.OverrideConstructor, id); + createdFromNonDefaultCreator = true; + return CreateObjectUsingCreatorWithParameters(reader, objectContract, containerMember, objectContract.OverrideCreator, id); } - newObject = objectContract.OverrideConstructor.Invoke(null); + newObject = objectContract.OverrideCreator(new object[0]); } else if (objectContract.DefaultCreator != null && - (!objectContract.DefaultCreatorNonPublic || Serializer._constructorHandling == ConstructorHandling.AllowNonPublicDefaultConstructor || objectContract.ParametrizedConstructor == null)) + (!objectContract.DefaultCreatorNonPublic || Serializer._constructorHandling == ConstructorHandling.AllowNonPublicDefaultConstructor || objectContract.ParametrizedCreator == null)) { // use the default constructor if it is... // public // non-public and the user has change constructor handling settings - // non-public and there is no other constructor + // non-public and there is no other creator newObject = objectContract.DefaultCreator(); } - else if (objectContract.ParametrizedConstructor != null) + else if (objectContract.ParametrizedCreator != null) { - createdFromNonDefaultConstructor = true; - return CreateObjectFromNonDefaultConstructor(reader, objectContract, containerMember, objectContract.ParametrizedConstructor, id); + createdFromNonDefaultCreator = true; + return CreateObjectUsingCreatorWithParameters(reader, objectContract, containerMember, objectContract.ParametrizedCreator, id); } if (newObject == null) throw JsonSerializationException.Create(reader, "Unable to find a constructor to use for type {0}. A class should either have a default constructor, one constructor with arguments or a constructor marked with the JsonConstructor attribute.".FormatWith(CultureInfo.InvariantCulture, objectContract.UnderlyingType)); - createdFromNonDefaultConstructor = false; + createdFromNonDefaultCreator = false; return newObject; } diff --git a/Src/Newtonsoft.Json/Utilities/DynamicReflectionDelegateFactory.cs b/Src/Newtonsoft.Json/Utilities/DynamicReflectionDelegateFactory.cs index 7569e25d..df013742 100644 --- a/Src/Newtonsoft.Json/Utilities/DynamicReflectionDelegateFactory.cs +++ b/Src/Newtonsoft.Json/Utilities/DynamicReflectionDelegateFactory.cs @@ -78,13 +78,49 @@ namespace Newtonsoft.Json.Utilities if (!method.IsConstructor && !method.IsStatic) generator.PushInstance(method.DeclaringType); + int localVariableCount = 0; + for (int i = 0; i < args.Length; i++) { + Type parameterType = args[i].ParameterType; + generator.Emit(OpCodes.Ldarg_1); + generator.Emit(OpCodes.Ldc_I4, i); generator.Emit(OpCodes.Ldelem_Ref); - generator.UnboxIfNeeded(args[i].ParameterType); + if (parameterType.IsValueType()) + { + // have to check that value type parameters aren't null + // otherwise they will error when unboxed + Label skipSettingDefault = generator.DefineLabel(); + Label finishedProcessingParameter = generator.DefineLabel(); + + // check if parameter is not null + generator.Emit(OpCodes.Brtrue_S, skipSettingDefault); + + // parameter has no value, initialize to default + LocalBuilder localVariable = generator.DeclareLocal(parameterType); + generator.Emit(OpCodes.Ldloca_S, localVariable); + generator.Emit(OpCodes.Initobj, parameterType); + generator.Emit(OpCodes.Ldloc, localVariableCount); + generator.Emit(OpCodes.Br_S, finishedProcessingParameter); + + // parameter has value, get value from array again and unbox + generator.MarkLabel(skipSettingDefault); + generator.Emit(OpCodes.Ldarg_1); + generator.Emit(OpCodes.Ldc_I4, i); + generator.Emit(OpCodes.Ldelem_Ref); + generator.UnboxIfNeeded(parameterType); + + // parameter finished, we out! + generator.MarkLabel(finishedProcessingParameter); + localVariableCount++; + } + else + { + generator.UnboxIfNeeded(parameterType); + } } if (method.IsConstructor) diff --git a/Src/Newtonsoft.Json/Utilities/ExpressionReflectionDelegateFactory.cs b/Src/Newtonsoft.Json/Utilities/ExpressionReflectionDelegateFactory.cs index 296e86e3..448a84b8 100644 --- a/Src/Newtonsoft.Json/Utilities/ExpressionReflectionDelegateFactory.cs +++ b/Src/Newtonsoft.Json/Utilities/ExpressionReflectionDelegateFactory.cs @@ -54,13 +54,26 @@ namespace Newtonsoft.Json.Utilities for (int i = 0; i < parametersInfo.Length; i++) { + Type parameterType = parametersInfo[i].ParameterType; + Expression indexExpression = Expression.Constant(i); Expression paramAccessorExpression = Expression.ArrayIndex(argsParameterExpression, indexExpression); - paramAccessorExpression = EnsureCastExpression(paramAccessorExpression, parametersInfo[i].ParameterType); + Expression argExpression; - argsExpression[i] = paramAccessorExpression; + if (parameterType.IsValueType()) + { + BinaryExpression ensureValueTypeNotNull = Expression.Coalesce(paramAccessorExpression, Expression.New(parameterType)); + + argExpression = EnsureCastExpression(ensureValueTypeNotNull, parameterType); + } + else + { + argExpression = EnsureCastExpression(paramAccessorExpression, parameterType); + } + + argsExpression[i] = argExpression; } Expression callExpression;