Skip to content

Commit

Permalink
-Improved incorrect JSON deserialization error messages
Browse files Browse the repository at this point in the history
-Fixed constructor name result not being type checked
  • Loading branch information
JamesNK committed May 26, 2012
1 parent bdc7390 commit 2fbf5f7
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 22 deletions.
2 changes: 1 addition & 1 deletion Src/Newtonsoft.Json.Tests/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,5 +72,5 @@
// by using the '*' as shown below:
[assembly: AssemblyVersion("4.5.0.0")]
#if !PocketPC
[assembly: AssemblyFileVersion("4.5.5.14917")]
[assembly: AssemblyFileVersion("4.5.5.14927")]
#endif
122 changes: 116 additions & 6 deletions Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
using System.Runtime.Serialization.Json;
#endif
using Newtonsoft.Json.Serialization;
using Newtonsoft.Json.Tests.Linq;
using Newtonsoft.Json.Tests.TestObjects;
using System.Runtime.Serialization;
using System.Globalization;
Expand Down Expand Up @@ -2017,24 +2018,133 @@ public void CannotDeserializeArrayIntoObject()
string json = @"[]";

ExceptionAssert.Throws<JsonSerializationException>(
@"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. Path '', line 1, position 1.",
@"Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'Newtonsoft.Json.Tests.TestObjects.Person' because the type requires a JSON object (e.g. {""name"":""value""}) to deserialize correctly.
To fix this error either change the JSON to what is required or change the deserialized type to an array or a type that implements a collection interface (e.g. IEnumerable, ICollection, IList) like List<T>.
Another option is to add the [JsonArray] attribute to the type to force it to work with a JSON array.
Path '', line 1, position 1.",
() =>
{
JsonConvert.DeserializeObject<Person>(json);
});
}

[Test]
public void CannotDeserializeArrayIntoDictionary()
{
string json = @"[]";

ExceptionAssert.Throws<JsonSerializationException>(
@"Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'System.Collections.Generic.Dictionary`2[System.String,System.String]' because the type requires a JSON object (e.g. {""name"":""value""}) to deserialize correctly.
To fix this error either change the JSON to what is required or change the deserialized type to an array or a type that implements a collection interface (e.g. IEnumerable, ICollection, IList) like List<T>.
Another option is to add the [JsonArray] attribute to the type to force it to work with a JSON array.
Path '', line 1, position 1.",
() =>
{
JsonConvert.DeserializeObject<Dictionary<string, string>>(json);
});
}

#if !(SILVERLIGHT || NETFX_CORE || PORTABLE)
[Test]
public void CannotDeserializeArrayIntoSerializable()
{
string json = @"[]";

ExceptionAssert.Throws<JsonSerializationException>(
@"Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'System.Exception' because the type requires a JSON object (e.g. {""name"":""value""}) to deserialize correctly.
To fix this error either change the JSON to what is required or change the deserialized type to an array or a type that implements a collection interface (e.g. IEnumerable, ICollection, IList) like List<T>.
Another option is to add the [JsonArray] attribute to the type to force it to work with a JSON array.
Path '', line 1, position 1.",
() =>
{
JsonConvert.DeserializeObject<Exception>(json);
});
}
#endif

[Test]
public void CannotDeserializeArrayIntoDouble()
{
string json = @"[]";

ExceptionAssert.Throws<JsonSerializationException>(
@"Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'System.Double' because the type requires a JSON primitive value (e.g. a string or a number) to deserialize correctly.
To fix this error either change the JSON to what is required or change the deserialized type to an array or a type that implements a collection interface (e.g. IEnumerable, ICollection, IList) like List<T>.
Another option is to add the [JsonArray] attribute to the type to force it to work with a JSON array.
Path '', line 1, position 1.",
() =>
{
JsonConvert.DeserializeObject<double>(json);
});
}

#if !(NET35 || NET20 || WINDOWS_PHONE || PORTABLE)
[Test]
public void CannotDeserializeArrayIntoDynamic()
{
string json = @"[]";

ExceptionAssert.Throws<JsonSerializationException>(
@"Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'Newtonsoft.Json.Tests.Linq.DynamicDictionary' because the type requires a JSON object (e.g. {""name"":""value""}) to deserialize correctly.
To fix this error either change the JSON to what is required or change the deserialized type to an array or a type that implements a collection interface (e.g. IEnumerable, ICollection, IList) like List<T>.
Another option is to add the [JsonArray] attribute to the type to force it to work with a JSON array.
Path '', line 1, position 1.",
() =>
{
JsonConvert.DeserializeObject<DynamicDictionary>(json);
});
}
#endif

[Test]
public void CannotDeserializeArrayIntoLinqToJson()
{
string json = @"[]";

ExceptionAssert.Throws<InvalidCastException>(
@"Unable to cast object of type 'Newtonsoft.Json.Linq.JArray' to type 'Newtonsoft.Json.Linq.JObject'.",
() =>
{
JsonConvert.DeserializeObject<JObject>(json);
});
}

[Test]
public void CannotDeserializeConstructorIntoObject()
{
string json = @"new Constructor(123)";

ExceptionAssert.Throws<JsonSerializationException>(
@"Error converting value ""Constructor"" to type 'Newtonsoft.Json.Tests.TestObjects.Person'. Path '', line 1, position 16.",
() =>
{
JsonConvert.DeserializeObject<Person>(json);
});
}

[Test]
public void CannotDeserializeConstructorIntoObjectNested()
{
string json = @"[new Constructor(123)]";

ExceptionAssert.Throws<JsonSerializationException>(
@"Error converting value ""Constructor"" to type 'Newtonsoft.Json.Tests.TestObjects.Person'. Path '[0]', line 1, position 17.",
() =>
{
JsonConvert.DeserializeObject<List<Person>>(json);
});
}

[Test]
public void CannotDeserializeObjectIntoArray()
{
string json = @"{}";

ExceptionAssert.Throws<JsonSerializationException>(
@"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. Path '', line 1, position 2.",
@"Cannot deserialize the current JSON object (e.g. {""name"":""value""}) into type 'System.Collections.Generic.List`1[Newtonsoft.Json.Tests.TestObjects.Person]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly.
To fix this error either change the JSON to what is required or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List<T>) or a dictionary type (e.g. Dictionary<TKey, TValue>).
Another option is to add the [JsonObject] attribute to the type to force it to work with a JSON object.
Path '', line 1, position 2.",
() =>
{
JsonConvert.DeserializeObject<List<Person>>(json);
Expand Down
14 changes: 10 additions & 4 deletions Src/Newtonsoft.Json/JsonException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,18 @@ public JsonException(SerializationInfo info, StreamingContext context)

internal static string FormatExceptionMessage(IJsonLineInfo lineInfo, string path, string message)
{
message = message.Trim();
// don't add a fullstop and space when message ends with a new line
if (!message.EndsWith(Environment.NewLine))
{
message = message.Trim();

if (!message.EndsWith("."))
message += ".";
if (!message.EndsWith("."))
message += ".";

message += " Path '{0}'".FormatWith(CultureInfo.InvariantCulture, path);
message += " ";
}

message += "Path '{0}'".FormatWith(CultureInfo.InvariantCulture, path);

if (lineInfo != null && lineInfo.HasLineInfo())
message += ", line {0}, position {1}".FormatWith(CultureInfo.InvariantCulture, lineInfo.LineNumber, lineInfo.LinePosition);
Expand Down
2 changes: 1 addition & 1 deletion Src/Newtonsoft.Json/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@
// by using the '*' as shown below:
[assembly: AssemblyVersion("4.5.0.0")]
#if !PocketPC
[assembly: AssemblyFileVersion("4.5.5.14917")]
[assembly: AssemblyFileVersion("4.5.5.14927")]
#endif

[assembly: CLSCompliant(true)]
47 changes: 37 additions & 10 deletions Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ private object CreateValueInternal(JsonReader reader, Type objectType, JsonContr
case JsonToken.StartConstructor:
string constructorName = reader.Value.ToString();

return constructorName;
return EnsureType(reader, constructorName, CultureInfo.InvariantCulture, contract, objectType);
case JsonToken.Null:
case JsonToken.Undefined:
#if !(NETFX_CORE || PORTABLE)
Expand All @@ -270,6 +270,30 @@ private object CreateValueInternal(JsonReader reader, Type objectType, JsonContr
throw JsonSerializationException.Create(reader, "Unexpected end when deserializing object.");
}

internal string GetExpectedDescription(JsonContract contract)
{
switch (contract.ContractType)
{
case JsonContractType.Object:
case JsonContractType.Dictionary:
#if !(SILVERLIGHT || NETFX_CORE || PORTABLE)
case JsonContractType.Serializable:
#endif
#if !(NET35 || NET20 || WINDOWS_PHONE || PORTABLE)
case JsonContractType.Dynamic:
#endif
return @"JSON object (e.g. {""name"":""value""})";
case JsonContractType.Array:
return @"JSON array (e.g. [1,2,3])";
case JsonContractType.Primitive:
return @"JSON primitive value (e.g. a string or a number)";
case JsonContractType.String:
return @"JSON string value";
default:
throw new ArgumentOutOfRangeException();
}
}

private JsonConverter GetConverter(JsonContract contract, JsonConverter memberConverter, JsonContainerContract containerContract, JsonProperty containerProperty)
{
JsonConverter converter = null;
Expand Down Expand Up @@ -361,9 +385,10 @@ private object CreateObject(JsonReader reader, Type objectType, JsonContract con
#endif
}

throw JsonSerializationException.Create(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));
throw JsonSerializationException.Create(reader, @"Cannot deserialize the current JSON object (e.g. {{""name"":""value""}}) into type '{0}' because the type requires a {1} to deserialize correctly.
To fix this error either change the JSON to what is required or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List<T>) or a dictionary type (e.g. Dictionary<TKey, TValue>).
Another option is to add the [JsonObject] attribute to the type to force it to work with a JSON object.
".FormatWith(CultureInfo.InvariantCulture, objectType, GetExpectedDescription(contract)));
}

private bool ReadSpecialProperties(JsonReader reader, ref Type objectType, ref JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, object existingValue, out object newValue, out string id)
Expand Down Expand Up @@ -492,9 +517,10 @@ private JsonArrayContract EnsureArrayContract(JsonReader reader, Type objectType

JsonArrayContract arrayContract = contract as JsonArrayContract;
if (arrayContract == null)
throw JsonSerializationException.Create(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));
throw JsonSerializationException.Create(reader, @"Cannot deserialize the current JSON array (e.g. [1,2,3]) into type '{0}' because the type requires a {1} to deserialize correctly.
To fix this error either change the JSON to what is required or change the deserialized type to an array or a type that implements a collection interface (e.g. IEnumerable, ICollection, IList) like List<T>.
Another option is to add the [JsonArray] attribute to the type to force it to work with a JSON array.
".FormatWith(CultureInfo.InvariantCulture, objectType, GetExpectedDescription(contract)));

return arrayContract;
}
Expand Down Expand Up @@ -913,7 +939,8 @@ private object CreateISerializable(JsonReader reader, JsonISerializableContract
if (!JsonTypeReflector.FullyTrusted)
{
throw JsonSerializationException.Create(reader, @"Type '{0}' implements ISerializable but cannot be deserialized using the ISerializable interface because the current application is not fully trusted and ISerializable can expose secure data.
To fix this error either change the environment to be fully trusted, change the application to not deserialize the type, add to JsonObjectAttribute to the type or change the JsonSerializer setting ContractResolver to use a new DefaultContractResolver with IgnoreSerializableInterface set to true.".FormatWith(CultureInfo.InvariantCulture, objectType));
To fix this error either change the environment to be fully trusted, change the application to not deserialize the type, add to JsonObjectAttribute to the type or change the JsonSerializer setting ContractResolver to use a new DefaultContractResolver with IgnoreSerializableInterface set to true.
".FormatWith(CultureInfo.InvariantCulture, objectType));
}

SerializationInfo serializationInfo = new SerializationInfo(contract.UnderlyingType, GetFormatterConverter());
Expand Down Expand Up @@ -1400,11 +1427,11 @@ private void SetPropertyPresence(JsonReader reader, JsonProperty property, Dicti
}
}

private void HandleError(JsonReader reader, bool readPasteError, int initialDepth)
private void HandleError(JsonReader reader, bool readPastError, int initialDepth)
{
ClearErrorContext();

if (readPasteError)
if (readPastError)
{
reader.Skip();

Expand Down

0 comments on commit 2fbf5f7

Please sign in to comment.