Skip to content
This repository has been archived by the owner on Jan 14, 2021. It is now read-only.

Commit

Permalink
-Changed serializer to fallback to use a private default constructor
Browse files Browse the repository at this point in the history
-Changed error message when deserializing a JSON object/array onto the wrong kind of type to be more descriptive
  • Loading branch information
JamesNK committed Mar 10, 2012
1 parent d32061c commit f9e5745
Show file tree
Hide file tree
Showing 10 changed files with 71 additions and 34 deletions.
13 changes: 7 additions & 6 deletions Doc/SerializationSettings.html
Expand Up @@ -405,10 +405,11 @@ <h3>ConstructorHandling</h3>
<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>
Expand All @@ -419,8 +420,8 @@ <h3>ConstructorHandling</h3>
</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>
Expand Down
15 changes: 0 additions & 15 deletions Src/Newtonsoft.Json.Tests/PerformanceTests.cs
Expand Up @@ -703,21 +703,6 @@ public void JObjectToString2()
}, "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()
{
Expand Down
2 changes: 1 addition & 1 deletion Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs
Expand Up @@ -72,5 +72,5 @@
// 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
Expand Up @@ -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]
Expand Down
36 changes: 34 additions & 2 deletions Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs
Expand Up @@ -1905,7 +1905,9 @@ public void DeserializeNullableListWithNulls()
}

[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 = @"[]";
Expand All @@ -1914,7 +1916,9 @@ public void CannotDeserializeArrayIntoObject()
}

[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 = @"{}";
Expand Down Expand Up @@ -5550,9 +5554,37 @@ public void NullableDeserializeUTC()
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; }
Expand Down
Expand Up @@ -33,5 +33,15 @@ public class 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)
{
}
}
}
4 changes: 2 additions & 2 deletions Src/Newtonsoft.Json/ConstructorHandling.cs
Expand Up @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion Src/Newtonsoft.Json/Properties/AssemblyInfo.cs
Expand Up @@ -85,7 +85,7 @@
// 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)]
2 changes: 1 addition & 1 deletion Src/Newtonsoft.Json/Serialization/JsonContract.cs
Expand Up @@ -119,7 +119,7 @@ public abstract class JsonContract
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; }
Expand Down
14 changes: 11 additions & 3 deletions Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs
Expand Up @@ -468,7 +468,9 @@ private object CreateObject(JsonReader reader, Type objectType, JsonContract con
#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)
Expand All @@ -478,7 +480,9 @@ private JsonArrayContract EnsureArrayContract(JsonReader reader, Type objectType

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;
}
Expand Down Expand Up @@ -954,8 +958,12 @@ private object CreateAndPopulateObject(JsonReader reader, JsonObjectContract con
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)
Expand Down

0 comments on commit f9e5745

Please sign in to comment.