From f9e5745425f3d4e7ca4da340f583a45c7d495660 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Sat, 10 Mar 2012 17:39:39 +1300 Subject: -Changed serializer to fallback to use a private default constructor -Changed error message when deserializing a JSON object/array onto the wrong kind of type to be more descriptive --- Doc/SerializationSettings.html | 13 ++++---- Src/Newtonsoft.Json.Tests/PerformanceTests.cs | 15 --------- .../Properties/AssemblyInfo.cs | 2 +- .../Serialization/ConstructorHandlingTests.cs | 7 +++-- .../Serialization/JsonSerializerTest.cs | 36 ++++++++++++++++++++-- .../TestObjects/PrivateConstructorTestClass.cs | 10 ++++++ Src/Newtonsoft.Json/ConstructorHandling.cs | 4 +-- Src/Newtonsoft.Json/Properties/AssemblyInfo.cs | 2 +- Src/Newtonsoft.Json/Serialization/JsonContract.cs | 2 +- .../Serialization/JsonSerializerInternalReader.cs | 14 +++++++-- 10 files changed, 71 insertions(+), 34 deletions(-) diff --git a/Doc/SerializationSettings.html b/Doc/SerializationSettings.html index 21a51c2..d11a80f 100644 --- a/Doc/SerializationSettings.html +++ b/Doc/SerializationSettings.html @@ -405,10 +405,11 @@ DefaultValueHandling controls how Json.NET uses default values set using the .NE
By default Json.NET will first look for a constructor marked with the JsonConstructorAttribute, then look for a public default - constructor (a constructor that doesn't take any arguments) and finally check if - the class has a single public constructor with arguments. If the class has - multiple public constructors with arguments an error will be thrown. This can be - fixed by marking one of the constructors with the JsonConstructorAttribute. + constructor (a constructor that doesn't take any arguments), then check if + the class has a single public constructor with arguments and finally check + for a non-public default constructor. If the class has multiple public + constructors with arguments an error will be thrown. This can be fixed by + marking one of the constructors with the JsonConstructorAttribute.

@@ -419,8 +420,8 @@ DefaultValueHandling controls how Json.NET uses default values set using the .NE
- Json.NET will fallback to - using a classes private default constructor if available. + Json.NET will use a classes private default constructor before constructors + with arguments if available.

diff --git a/Src/Newtonsoft.Json.Tests/PerformanceTests.cs b/Src/Newtonsoft.Json.Tests/PerformanceTests.cs index 7d3b8bd..268fe13 100644 --- a/Src/Newtonsoft.Json.Tests/PerformanceTests.cs +++ b/Src/Newtonsoft.Json.Tests/PerformanceTests.cs @@ -703,21 +703,6 @@ namespace Newtonsoft.Json.Tests }, "JObject.ToString"); } - [Test] - public void RecursiveLoop() - { - JArray a1 = new JArray(); - JArray a2 = new JArray(); - JArray a3 = new JArray(); - JArray a4 = new JArray(); - - a1.Add(a2); - a2.Add(a3); - a3.Add(a4); - - - } - [Test] public void NestedJToken() { diff --git a/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs index 7920d37..1646871 100644 --- a/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs +++ b/Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs @@ -72,5 +72,5 @@ using System.Security; // by using the '*' as shown below: [assembly: AssemblyVersion("4.0.8.0")] #if !PocketPC -[assembly: AssemblyFileVersion("4.0.8.14707")] +[assembly: AssemblyFileVersion("4.0.8.14710")] #endif diff --git a/Src/Newtonsoft.Json.Tests/Serialization/ConstructorHandlingTests.cs b/Src/Newtonsoft.Json.Tests/Serialization/ConstructorHandlingTests.cs index 1f8ade6..1d48a81 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/ConstructorHandlingTests.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/ConstructorHandlingTests.cs @@ -7,12 +7,13 @@ 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.TestObjects.PrivateConstructorTestClass. A class should either have a default constructor, one constructor with arguments or a constructor marked with the JsonConstructor attribute. Line 1, position 6.")] - public void FailWithPrivateConstructorAndDefault() + public void UsePrivateConstructorIfThereAreMultipleConstructorsWithParametersAndNothingToFallbackTo() { string json = @"{Name:""Name!""}"; - JsonConvert.DeserializeObject(json); + var c = JsonConvert.DeserializeObject(json); + + Assert.AreEqual("Name!", c.Name); } [Test] diff --git a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs index d568eec..0f7f7ff 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs @@ -1905,7 +1905,9 @@ keyword such as type of business."" } [Test] - [ExpectedException(typeof (JsonSerializationException), ExpectedMessage = @"Cannot deserialize JSON array into type 'Newtonsoft.Json.Tests.TestObjects.Person'. Line 1, position 1.")] + [ExpectedException(typeof(JsonSerializationException), ExpectedMessage = @"Cannot deserialize JSON array (i.e. [1,2,3]) into type 'Newtonsoft.Json.Tests.TestObjects.Person'. +The deserialized type must be an array or implement a collection interface like IEnumerable, ICollection or IList. +To force JSON arrays to deserialize add the JsonArrayAttribute to the type. Line 1, position 1.")] public void CannotDeserializeArrayIntoObject() { string json = @"[]"; @@ -1914,7 +1916,9 @@ keyword such as type of business."" } [Test] - [ExpectedException(typeof (JsonSerializationException), ExpectedMessage = @"Cannot deserialize JSON object into type 'System.Collections.Generic.List`1[Newtonsoft.Json.Tests.TestObjects.Person]'. Line 1, position 2.")] + [ExpectedException(typeof (JsonSerializationException), ExpectedMessage = @"Cannot deserialize JSON object (i.e. {""name"":""value""}) into type 'System.Collections.Generic.List`1[Newtonsoft.Json.Tests.TestObjects.Person]'. +The deserialized type should be a normal .NET type (i.e. not a primitive type like integer, not a collection type like an array or List) or a dictionary type (i.e. Dictionary). +To force JSON objects to deserialize add the JsonObjectAttribute to the type. Line 1, position 2.")] public void CannotDeserializeObjectIntoArray() { string json = @"{}"; @@ -5550,9 +5554,37 @@ Parameter name: value")] Assert.AreEqual("Pre", c2.PreField); Assert.AreEqual("Post", c2.PostField); } + + [Test] + public void PrivateConstructor() + { + var person = PersonWithPrivateConstructor.CreatePerson(); + person.Name = "John Doe"; + person.Age = 25; + + var serializedPerson = JsonConvert.SerializeObject(person); + var roundtrippedPerson = JsonConvert.DeserializeObject(serializedPerson); + + Assert.AreEqual(person.Name, roundtrippedPerson.Name); + } #endif } + public class PersonWithPrivateConstructor + { + private PersonWithPrivateConstructor() + { } + + public static PersonWithPrivateConstructor CreatePerson() + { + return new PersonWithPrivateConstructor(); + } + + public string Name { get; set; } + + public int Age { get; set; } + } + public class DateTimeWrapper { public DateTime Value { get; set; } diff --git a/Src/Newtonsoft.Json.Tests/TestObjects/PrivateConstructorTestClass.cs b/Src/Newtonsoft.Json.Tests/TestObjects/PrivateConstructorTestClass.cs index ad52d17..698406d 100644 --- a/Src/Newtonsoft.Json.Tests/TestObjects/PrivateConstructorTestClass.cs +++ b/Src/Newtonsoft.Json.Tests/TestObjects/PrivateConstructorTestClass.cs @@ -33,5 +33,15 @@ namespace Newtonsoft.Json.Tests.TestObjects private PrivateConstructorTestClass() { } + + // multiple constructors with arguments so the serializer doesn't know what to fall back to + private PrivateConstructorTestClass(object a) + { + } + + // multiple constructors with arguments so the serializer doesn't know what to fall back to + private PrivateConstructorTestClass(object a, object b) + { + } } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json/ConstructorHandling.cs b/Src/Newtonsoft.Json/ConstructorHandling.cs index 591d150..123977b 100644 --- a/Src/Newtonsoft.Json/ConstructorHandling.cs +++ b/Src/Newtonsoft.Json/ConstructorHandling.cs @@ -36,11 +36,11 @@ namespace Newtonsoft.Json public enum ConstructorHandling { /// - /// First attempt to use the public default constructor then fall back to single paramatized constructor. + /// First attempt to use the public default constructor, then fall back to single paramatized constructor, then the non-public default constructor. /// Default = 0, /// - /// Allow Json.NET to use a non-public default constructor. + /// Json.NET will use a non-public default constructor before falling back to a paramatized constructor. /// AllowNonPublicDefaultConstructor = 1 } diff --git a/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs index 73df4a0..4f81f4d 100644 --- a/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs +++ b/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs @@ -85,7 +85,7 @@ using System.Security; // by using the '*' as shown below: [assembly: AssemblyVersion("4.0.8.0")] #if !PocketPC -[assembly: AssemblyFileVersion("4.0.8.14707")] +[assembly: AssemblyFileVersion("4.0.8.14710")] #endif [assembly: CLSCompliant(true)] diff --git a/Src/Newtonsoft.Json/Serialization/JsonContract.cs b/Src/Newtonsoft.Json/Serialization/JsonContract.cs index db38243..8a399a4 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonContract.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonContract.cs @@ -119,7 +119,7 @@ namespace Newtonsoft.Json.Serialization public Func DefaultCreator { get; set; } /// - /// Gets or sets a value indicating whether [default creator non public]. + /// Gets or sets a value indicating whether the default creator is non public. /// /// true if the default object creator is non-public; otherwise, false. public bool DefaultCreatorNonPublic { get; set; } diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs index 2a81a8a..7556323 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs @@ -468,7 +468,9 @@ namespace Newtonsoft.Json.Serialization #endif } - throw CreateSerializationException(reader, "Cannot deserialize JSON object into type '{0}'.".FormatWith(CultureInfo.InvariantCulture, objectType)); + throw CreateSerializationException(reader, @"Cannot deserialize JSON object (i.e. {{""name"":""value""}}) into type '{0}'. +The deserialized type should be a normal .NET type (i.e. not a primitive type like integer, not a collection type like an array or List) or a dictionary type (i.e. Dictionary). +To force JSON objects to deserialize add the JsonObjectAttribute to the type.".FormatWith(CultureInfo.InvariantCulture, objectType)); } private JsonArrayContract EnsureArrayContract(JsonReader reader, Type objectType, JsonContract contract) @@ -478,7 +480,9 @@ namespace Newtonsoft.Json.Serialization JsonArrayContract arrayContract = contract as JsonArrayContract; if (arrayContract == null) - throw CreateSerializationException(reader, "Cannot deserialize JSON array into type '{0}'.".FormatWith(CultureInfo.InvariantCulture, objectType)); + throw CreateSerializationException(reader, @"Cannot deserialize JSON array (i.e. [1,2,3]) into type '{0}'. +The deserialized type must be an array or implement a collection interface like IEnumerable, ICollection or IList. +To force JSON arrays to deserialize add the JsonArrayAttribute to the type.".FormatWith(CultureInfo.InvariantCulture, objectType)); return arrayContract; } @@ -954,8 +958,12 @@ namespace Newtonsoft.Json.Serialization newObject = contract.OverrideConstructor.Invoke(null); } else if (contract.DefaultCreator != null && - (!contract.DefaultCreatorNonPublic || Serializer.ConstructorHandling == ConstructorHandling.AllowNonPublicDefaultConstructor)) + (!contract.DefaultCreatorNonPublic || Serializer.ConstructorHandling == ConstructorHandling.AllowNonPublicDefaultConstructor || contract.ParametrizedConstructor == 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 newObject = contract.DefaultCreator(); } else if (contract.ParametrizedConstructor != null) -- cgit v1.2.3