-Added support for missing JSON errors to error handling

This commit is contained in:
JamesNK
2011-07-31 23:10:38 +00:00
parent 2da612396d
commit ce2b5e02e0
2 changed files with 124 additions and 68 deletions
@@ -25,11 +25,13 @@
#if !PocketPC
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text;
using Newtonsoft.Json.Serialization;
using Newtonsoft.Json.Tests;
using Newtonsoft.Json.Tests.TestObjects;
using NUnit.Framework;
@@ -288,6 +290,47 @@ namespace Newtonsoft.Json.Tests.Serialization
}", json);
}
#endif
public void WhenSerializationErrorDetectedBySerializer_ThenCallbackIsCalled()
{
// Verify contract is properly finding our callback
var resolver = new DefaultContractResolver().ResolveContract(typeof(FooEvent));
Debug.Assert(resolver.OnError != null);
Debug.Assert(resolver.OnError == typeof(FooEvent).GetMethod("OnError", BindingFlags.Instance | BindingFlags.NonPublic));
var serializer = JsonSerializer.Create(new JsonSerializerSettings
{
// If I don't specify Error here, the callback isn't called
// either, but no exception is thrown.
MissingMemberHandling = MissingMemberHandling.Error,
});
// This throws with missing member exception, rather than calling my callback.
var foo = serializer.Deserialize<FooEvent>(new JsonTextReader(new StringReader("{ Id: 25 }")));
// When fixed, this would pass.
Debug.Assert(foo.Identifier == 25);
}
public class FooEvent
{
public int Identifier { get; set; }
[OnError]
private void OnError(StreamingContext context, ErrorContext error)
{
this.Identifier = 25;
// Here we could for example manually copy the
// persisted "Id" value into the renamed "Identifier"
// property, etc.
error.Handled = true;
// We never get here :(
Console.WriteLine("Error has been fixed");
}
}
}
}
#endif
@@ -65,7 +65,7 @@ namespace Newtonsoft.Json.Serialization
if (reader.TokenType == JsonToken.StartArray)
{
if (contract is JsonArrayContract)
PopulateList(CollectionUtils.CreateCollectionWrapper(target), reader, null, (JsonArrayContract)contract);
PopulateList(CollectionUtils.CreateCollectionWrapper(target), reader, null, (JsonArrayContract) contract);
else
throw new JsonSerializationException("Cannot populate JSON array onto type '{0}'.".FormatWith(CultureInfo.InvariantCulture, objectType));
}
@@ -82,9 +82,9 @@ namespace Newtonsoft.Json.Serialization
}
if (contract is JsonDictionaryContract)
PopulateDictionary(CollectionUtils.CreateDictionaryWrapper(target), reader, (JsonDictionaryContract)contract, id);
PopulateDictionary(CollectionUtils.CreateDictionaryWrapper(target), reader, (JsonDictionaryContract) contract, id);
else if (contract is JsonObjectContract)
PopulateObject(target, reader, (JsonObjectContract)contract, id);
PopulateObject(target, reader, (JsonObjectContract) contract, id);
else
throw new JsonSerializationException("Cannot populate JSON object onto type '{0}'.".FormatWith(CultureInfo.InvariantCulture, objectType));
}
@@ -143,7 +143,7 @@ namespace Newtonsoft.Json.Serialization
{
ValidationUtils.ArgumentNotNull(reader, "reader");
if (contract != null && contract.UnderlyingType == typeof(JRaw))
if (contract != null && contract.UnderlyingType == typeof (JRaw))
{
return JRaw.Create(reader);
}
@@ -218,8 +218,8 @@ namespace Newtonsoft.Json.Serialization
{
switch (reader.TokenType)
{
// populate a typed object or generic dictionary/array
// depending upon whether an objectType was supplied
// populate a typed object or generic dictionary/array
// depending upon whether an objectType was supplied
case JsonToken.StartObject:
return CreateObject(reader, objectType, contract, member, existingValue);
case JsonToken.StartArray:
@@ -232,14 +232,14 @@ namespace Newtonsoft.Json.Serialization
return EnsureType(reader.Value, CultureInfo.InvariantCulture, objectType);
case JsonToken.String:
// convert empty string to null automatically for nullable types
if (string.IsNullOrEmpty((string)reader.Value) &&
if (string.IsNullOrEmpty((string) reader.Value) &&
objectType != null &&
ReflectionUtils.IsNullableType(objectType))
ReflectionUtils.IsNullableType(objectType))
return null;
// string that needs to be returned as a byte array should be base 64 decoded
if (objectType == typeof(byte[]))
return Convert.FromBase64String((string)reader.Value);
if (objectType == typeof (byte[]))
return Convert.FromBase64String((string) reader.Value);
return EnsureType(reader.Value, CultureInfo.InvariantCulture, objectType);
case JsonToken.StartConstructor:
@@ -249,12 +249,12 @@ namespace Newtonsoft.Json.Serialization
return constructorName;
case JsonToken.Null:
case JsonToken.Undefined:
if (objectType == typeof(DBNull))
if (objectType == typeof (DBNull))
return DBNull.Value;
return EnsureType(reader.Value, CultureInfo.InvariantCulture, objectType);
case JsonToken.Raw:
return new JRaw((string)reader.Value);
return new JRaw((string) reader.Value);
case JsonToken.Comment:
// ignore
break;
@@ -372,7 +372,7 @@ namespace Newtonsoft.Json.Serialization
specialProperty = false;
}
} while (specialProperty
&& reader.TokenType == JsonToken.PropertyName);
&& reader.TokenType == JsonToken.PropertyName);
}
if (!HasDefinedType(objectType))
@@ -471,11 +471,11 @@ namespace Newtonsoft.Json.Serialization
private bool HasDefinedType(Type type)
{
return (type != null && type != typeof(object) && !typeof(JToken).IsAssignableFrom(type)
return (type != null && type != typeof (object) && !typeof (JToken).IsAssignableFrom(type)
#if !(NET35 || NET20 || WINDOWS_PHONE)
&& type != typeof(IDynamicMetaObjectProvider)
&& type != typeof (IDynamicMetaObjectProvider)
#endif
);
);
}
private object EnsureType(object value, CultureInfo culture, Type targetType)
@@ -530,15 +530,15 @@ namespace Newtonsoft.Json.Serialization
if ((objectCreationHandling == ObjectCreationHandling.Auto || objectCreationHandling == ObjectCreationHandling.Reuse)
&& (reader.TokenType == JsonToken.StartArray || reader.TokenType == JsonToken.StartObject)
&& property.Readable)
&& property.Readable)
{
currentValue = property.ValueProvider.GetValue(target);
gottenCurrentValue = true;
useExistingValue = (currentValue != null
&& !property.PropertyType.IsArray
&& !ReflectionUtils.InheritsGenericDefinition(property.PropertyType, typeof(ReadOnlyCollection<>))
&& !property.PropertyType.IsValueType);
&& !ReflectionUtils.InheritsGenericDefinition(property.PropertyType, typeof (ReadOnlyCollection<>))
&& !property.PropertyType.IsValueType);
}
if (!property.Writable && !useExistingValue)
@@ -557,7 +557,7 @@ namespace Newtonsoft.Json.Serialization
// test tokentype here because default value might not be convertable to actual type, e.g. default of "" for DateTime
if (property.DefaultValueHandling.GetValueOrDefault(Serializer.DefaultValueHandling) == DefaultValueHandling.Ignore
&& JsonReader.IsPrimitiveToken(reader.TokenType)
&& Equals(reader.Value, property.DefaultValue))
&& Equals(reader.Value, property.DefaultValue))
{
reader.Skip();
return;
@@ -624,21 +624,21 @@ namespace Newtonsoft.Json.Serialization
switch (reader.TokenType)
{
case JsonToken.PropertyName:
object keyValue;
object keyValue = reader.Value;
try
{
keyValue = EnsureType(reader.Value, CultureInfo.InvariantCulture, contract.DictionaryKeyType);
}
catch (Exception ex)
{
throw new JsonSerializationException("Could not convert string '{0}' to dictionary key type '{1}'. Create a TypeConverter to convert from the string to the key type object.".FormatWith(CultureInfo.InvariantCulture, reader.Value, contract.DictionaryKeyType), ex);
}
try
{
keyValue = EnsureType(keyValue, CultureInfo.InvariantCulture, contract.DictionaryKeyType);
}
catch (Exception ex)
{
throw new JsonSerializationException("Could not convert string '{0}' to dictionary key type '{1}'. Create a TypeConverter to convert from the string to the key type object.".FormatWith(CultureInfo.InvariantCulture, reader.Value, contract.DictionaryKeyType), ex);
}
if (!ReadForType(reader, contract.DictionaryValueType, null))
throw new JsonSerializationException("Unexpected end when deserializing object.");
if (!ReadForType(reader, contract.DictionaryValueType, null))
throw new JsonSerializationException("Unexpected end when deserializing object.");
try
{
dictionary[keyValue] = CreateValueNonProperty(reader, contract.DictionaryValueType, GetContractSafe(contract.DictionaryValueType));
}
catch (Exception ex)
@@ -802,7 +802,7 @@ namespace Newtonsoft.Json.Serialization
if (contract.DefaultCreator != null &&
(!contract.DefaultCreatorNonPublic || Serializer.ConstructorHandling == ConstructorHandling.AllowNonPublicDefaultConstructor))
newObject = (IDynamicMetaObjectProvider)contract.DefaultCreator();
newObject = (IDynamicMetaObjectProvider) contract.DefaultCreator();
else
throw new JsonSerializationException("Unable to find a default constructor to use for type {0}.".FormatWith(CultureInfo.InvariantCulture, contract.UnderlyingType));
@@ -811,6 +811,8 @@ namespace Newtonsoft.Json.Serialization
contract.InvokeOnDeserializing(newObject, Serializer.Context);
int initialDepth = reader.Depth;
bool exit = false;
do
{
@@ -818,22 +820,33 @@ namespace Newtonsoft.Json.Serialization
{
case JsonToken.PropertyName:
string memberName = reader.Value.ToString();
if (!reader.Read())
throw new JsonSerializationException("Unexpected end when setting {0}'s value.".FormatWith(CultureInfo.InvariantCulture, memberName));
// first attempt to find a settable property, otherwise fall back to a dynamic set without type
JsonProperty property = contract.Properties.GetClosestMatchProperty(memberName);
if (property != null && property.Writable && !property.Ignored)
try
{
SetPropertyValue(property, reader, newObject);
if (!reader.Read())
throw new JsonSerializationException("Unexpected end when setting {0}'s value.".FormatWith(CultureInfo.InvariantCulture, memberName));
// first attempt to find a settable property, otherwise fall back to a dynamic set without type
JsonProperty property = contract.Properties.GetClosestMatchProperty(memberName);
if (property != null && property.Writable && !property.Ignored)
{
SetPropertyValue(property, reader, newObject);
}
else
{
Type t = (JsonReader.IsPrimitiveToken(reader.TokenType)) ? reader.ValueType : typeof (IDynamicMetaObjectProvider);
object value = CreateValueNonProperty(reader, t, GetContractSafe(t, null));
newObject.TrySetMember(memberName, value);
}
}
else
catch (Exception ex)
{
Type t = (JsonReader.IsPrimitiveToken(reader.TokenType)) ? reader.ValueType : typeof(IDynamicMetaObjectProvider);
object value = CreateValueNonProperty(reader, t, GetContractSafe(t, null));
newObject.TrySetMember(memberName, value);
if (IsErrorHandled(newObject, contract, memberName, ex))
HandleError(reader, initialDepth);
else
throw;
}
break;
case JsonToken.EndObject:
@@ -889,7 +902,7 @@ namespace Newtonsoft.Json.Serialization
IDictionary<JsonProperty, object> propertyValues = ResolvePropertyAndConstructorValues(contract, reader, objectType);
IDictionary<ParameterInfo, object> constructorParameters = constructorInfo.GetParameters().ToDictionary(p => p, p => (object)null);
IDictionary<ParameterInfo, object> constructorParameters = constructorInfo.GetParameters().ToDictionary(p => p, p => (object) null);
IDictionary<JsonProperty, object> remainingPropertyValues = new Dictionary<JsonProperty, object>();
foreach (KeyValuePair<JsonProperty, object> propertyValue in propertyValues)
@@ -976,7 +989,7 @@ namespace Newtonsoft.Json.Serialization
// attempt exact case match first
// then try match ignoring case
JsonProperty property = contract.ConstructorParameters.GetClosestMatchProperty(memberName) ??
contract.Properties.GetClosestMatchProperty(memberName);
contract.Properties.GetClosestMatchProperty(memberName);
if (property != null)
{
@@ -1000,7 +1013,7 @@ namespace Newtonsoft.Json.Serialization
}
break;
case JsonToken.Comment:
break;
break;
case JsonToken.EndObject:
exit = true;
break;
@@ -1021,18 +1034,18 @@ namespace Newtonsoft.Json.Serialization
if (hasConverter)
return reader.Read();
if (t == typeof(byte[]))
if (t == typeof (byte[]))
{
reader.ReadAsBytes();
return true;
}
else if ((t == typeof(decimal) || t == typeof(decimal?)))
else if ((t == typeof (decimal) || t == typeof (decimal?)))
{
reader.ReadAsDecimal();
return true;
}
#if !NET20
else if ((t == typeof(DateTimeOffset) || t == typeof(DateTimeOffset?)))
else if ((t == typeof (DateTimeOffset) || t == typeof (DateTimeOffset?)))
{
reader.ReadAsDateTimeOffset();
return true;
@@ -1067,26 +1080,26 @@ namespace Newtonsoft.Json.Serialization
case JsonToken.PropertyName:
string memberName = reader.Value.ToString();
// attempt exact case match first
// then try match ignoring case
JsonProperty property = contract.Properties.GetClosestMatchProperty(memberName);
if (property == null)
{
if (Serializer.MissingMemberHandling == MissingMemberHandling.Error)
throw new JsonSerializationException("Could not find member '{0}' on object of type '{1}'".FormatWith(CultureInfo.InvariantCulture, memberName, contract.UnderlyingType.Name));
reader.Skip();
continue;
}
if (!ReadForType(reader, property.PropertyType, property.Converter))
throw new JsonSerializationException("Unexpected end when setting {0}'s value.".FormatWith(CultureInfo.InvariantCulture, memberName));
SetRequiredProperty(reader, property, requiredProperties);
try
{
// attempt exact case match first
// then try match ignoring case
JsonProperty property = contract.Properties.GetClosestMatchProperty(memberName);
if (property == null)
{
if (Serializer.MissingMemberHandling == MissingMemberHandling.Error)
throw new JsonSerializationException("Could not find member '{0}' on object of type '{1}'".FormatWith(CultureInfo.InvariantCulture, memberName, contract.UnderlyingType.Name));
reader.Skip();
continue;
}
if (!ReadForType(reader, property.PropertyType, property.Converter))
throw new JsonSerializationException("Unexpected end when setting {0}'s value.".FormatWith(CultureInfo.InvariantCulture, memberName));
SetRequiredProperty(reader, property, requiredProperties);
SetPropertyValue(property, reader, newObject);
}
catch (Exception ex)