-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
This commit is contained in:
James Newton-King
2012-03-10 17:39:39 +13:00
parent d32061cd16
commit f9e5745425
10 changed files with 71 additions and 34 deletions
+7 -6
View File
@@ -405,10 +405,11 @@ DefaultValueHandling controls how Json.NET uses default values set using the .NE
<div class="summary"> <div class="summary">
By default Json.NET will first look for a constructor By default Json.NET will first look for a constructor
marked with the JsonConstructorAttribute, then look for a public default marked with the JsonConstructorAttribute, then look for a public default
constructor (a constructor that doesn't take any arguments) and finally check if constructor (a constructor that doesn't take any arguments), then check if
the class has a single public constructor with arguments. If the class has the class has a single public constructor with arguments and finally check
multiple public constructors with arguments an error will be thrown. This can be for a non-public default constructor. If the class has multiple public
fixed by marking one of the constructors with the JsonConstructorAttribute. constructors with arguments an error will be thrown. This can be fixed by
marking one of the constructors with the JsonConstructorAttribute.
</div> </div>
<br> <br>
</td> </td>
@@ -419,8 +420,8 @@ DefaultValueHandling controls how Json.NET uses default values set using the .NE
</td> </td>
<td> <td>
<div class="summary"> <div class="summary">
Json.NET will fallback to Json.NET will use a classes private default constructor before constructors
using a classes private default constructor if available. with arguments if available.
</div> </div>
<br> <br>
</td> </td>
@@ -703,21 +703,6 @@ namespace Newtonsoft.Json.Tests
}, "JObject.ToString"); }, "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] [Test]
public void NestedJToken() public void NestedJToken()
{ {
@@ -72,5 +72,5 @@ using System.Security;
// by using the '*' as shown below: // by using the '*' as shown below:
[assembly: AssemblyVersion("4.0.8.0")] [assembly: AssemblyVersion("4.0.8.0")]
#if !PocketPC #if !PocketPC
[assembly: AssemblyFileVersion("4.0.8.14707")] [assembly: AssemblyFileVersion("4.0.8.14710")]
#endif #endif
@@ -7,12 +7,13 @@ namespace Newtonsoft.Json.Tests.Serialization
public class ConstructorHandlingTests : TestFixtureBase public class ConstructorHandlingTests : TestFixtureBase
{ {
[Test] [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 UsePrivateConstructorIfThereAreMultipleConstructorsWithParametersAndNothingToFallbackTo()
public void FailWithPrivateConstructorAndDefault()
{ {
string json = @"{Name:""Name!""}"; string json = @"{Name:""Name!""}";
JsonConvert.DeserializeObject<PrivateConstructorTestClass>(json); var c = JsonConvert.DeserializeObject<PrivateConstructorTestClass>(json);
Assert.AreEqual("Name!", c.Name);
} }
[Test] [Test]
@@ -1905,7 +1905,9 @@ keyword such as type of business.""
} }
[Test] [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() public void CannotDeserializeArrayIntoObject()
{ {
string json = @"[]"; string json = @"[]";
@@ -1914,7 +1916,9 @@ keyword such as type of business.""
} }
[Test] [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<T>) or a dictionary type (i.e. Dictionary<TKey, TValue>).
To force JSON objects to deserialize add the JsonObjectAttribute to the type. Line 1, position 2.")]
public void CannotDeserializeObjectIntoArray() public void CannotDeserializeObjectIntoArray()
{ {
string json = @"{}"; string json = @"{}";
@@ -5550,9 +5554,37 @@ Parameter name: value")]
Assert.AreEqual("Pre", c2.PreField); Assert.AreEqual("Pre", c2.PreField);
Assert.AreEqual("Post", c2.PostField); 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<PersonWithPrivateConstructor>(serializedPerson);
Assert.AreEqual(person.Name, roundtrippedPerson.Name);
}
#endif #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 class DateTimeWrapper
{ {
public DateTime Value { get; set; } public DateTime Value { get; set; }
@@ -33,5 +33,15 @@ namespace Newtonsoft.Json.Tests.TestObjects
private PrivateConstructorTestClass() 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)
{
}
} }
} }
+2 -2
View File
@@ -36,11 +36,11 @@ namespace Newtonsoft.Json
public enum ConstructorHandling public enum ConstructorHandling
{ {
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
Default = 0, Default = 0,
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
AllowNonPublicDefaultConstructor = 1 AllowNonPublicDefaultConstructor = 1
} }
@@ -85,7 +85,7 @@ using System.Security;
// by using the '*' as shown below: // by using the '*' as shown below:
[assembly: AssemblyVersion("4.0.8.0")] [assembly: AssemblyVersion("4.0.8.0")]
#if !PocketPC #if !PocketPC
[assembly: AssemblyFileVersion("4.0.8.14707")] [assembly: AssemblyFileVersion("4.0.8.14710")]
#endif #endif
[assembly: CLSCompliant(true)] [assembly: CLSCompliant(true)]
@@ -119,7 +119,7 @@ namespace Newtonsoft.Json.Serialization
public Func<object> DefaultCreator { get; set; } public Func<object> DefaultCreator { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether [default creator non public]. /// Gets or sets a value indicating whether the default creator is non public.
/// </summary> /// </summary>
/// <value><c>true</c> if the default object creator is non-public; otherwise, <c>false</c>.</value> /// <value><c>true</c> if the default object creator is non-public; otherwise, <c>false</c>.</value>
public bool DefaultCreatorNonPublic { get; set; } public bool DefaultCreatorNonPublic { get; set; }
@@ -468,7 +468,9 @@ namespace Newtonsoft.Json.Serialization
#endif #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<T>) or a dictionary type (i.e. Dictionary<TKey, TValue>).
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) private JsonArrayContract EnsureArrayContract(JsonReader reader, Type objectType, JsonContract contract)
@@ -478,7 +480,9 @@ namespace Newtonsoft.Json.Serialization
JsonArrayContract arrayContract = contract as JsonArrayContract; JsonArrayContract arrayContract = contract as JsonArrayContract;
if (arrayContract == null) 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; return arrayContract;
} }
@@ -954,8 +958,12 @@ namespace Newtonsoft.Json.Serialization
newObject = contract.OverrideConstructor.Invoke(null); newObject = contract.OverrideConstructor.Invoke(null);
} }
else if (contract.DefaultCreator != 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(); newObject = contract.DefaultCreator();
} }
else if (contract.ParametrizedConstructor != null) else if (contract.ParametrizedConstructor != null)