-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">
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.
</div>
<br>
</td>
@@ -419,8 +420,8 @@ DefaultValueHandling controls how Json.NET uses default values set using the .NE
</td>
<td>
<div class="summary">
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.
</div>
<br>
</td>
@@ -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()
{
@@ -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
@@ -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<PrivateConstructorTestClass>(json);
var c = JsonConvert.DeserializeObject<PrivateConstructorTestClass>(json);
Assert.AreEqual("Name!", c.Name);
}
[Test]
@@ -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<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()
{
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<PersonWithPrivateConstructor>(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; }
@@ -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)
{
}
}
}
+2 -2
View File
@@ -36,11 +36,11 @@ namespace Newtonsoft.Json
public enum ConstructorHandling
{
/// <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>
Default = 0,
/// <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>
AllowNonPublicDefaultConstructor = 1
}
@@ -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)]
@@ -119,7 +119,7 @@ namespace Newtonsoft.Json.Serialization
public Func<object> DefaultCreator { get; set; }
/// <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>
/// <value><c>true</c> if the default object creator is non-public; otherwise, <c>false</c>.</value>
public bool DefaultCreatorNonPublic { get; set; }
@@ -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<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)
@@ -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)