-Improved incorrect JSON deserialization error messages

-Fixed constructor name result not being type checked
This commit is contained in:
James Newton-King
2012-05-27 01:09:59 +12:00
parent bdc73900b5
commit 2fbf5f7808
5 changed files with 165 additions and 22 deletions
@@ -72,5 +72,5 @@ using System.Security;
// 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
@@ -57,6 +57,7 @@ using Newtonsoft.Json.Converters;
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;
@@ -2017,24 +2018,133 @@ keyword such as type of business.""
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);
+10 -4
View File
@@ -84,12 +84,18 @@ namespace Newtonsoft.Json
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);
@@ -90,7 +90,7 @@ using System.Security;
// 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)]
@@ -248,7 +248,7 @@ namespace Newtonsoft.Json.Serialization
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)
@@ -270,6 +270,30 @@ namespace Newtonsoft.Json.Serialization
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;
@@ -361,9 +385,10 @@ namespace Newtonsoft.Json.Serialization
#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)
@@ -492,9 +517,10 @@ To force JSON objects to deserialize add the JsonObjectAttribute to the type.".F
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;
}
@@ -913,7 +939,8 @@ To force JSON arrays to deserialize add the JsonArrayAttribute to the type.".For
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());
@@ -1400,11 +1427,11 @@ To fix this error either change the environment to be fully trusted, change the
}
}
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();