diff --git a/Src/Newtonsoft.Json.Tests/Serialization/SerializationEventAttributeTests.cs b/Src/Newtonsoft.Json.Tests/Serialization/SerializationEventAttributeTests.cs index ed50d151..c440f499 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/SerializationEventAttributeTests.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/SerializationEventAttributeTests.cs @@ -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(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 \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs index c824ed33..17ca8287 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs @@ -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 propertyValues = ResolvePropertyAndConstructorValues(contract, reader, objectType); - IDictionary constructorParameters = constructorInfo.GetParameters().ToDictionary(p => p, p => (object)null); + IDictionary constructorParameters = constructorInfo.GetParameters().ToDictionary(p => p, p => (object) null); IDictionary remainingPropertyValues = new Dictionary(); foreach (KeyValuePair 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)