From 3e93c3813254122e2653f4a19217244512c60800 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Tue, 12 Feb 2013 01:43:54 -0800 Subject: [PATCH 1/6] -Enable multiple OnDeserialized's in JsonContract --- .../Serialization/JsonContract.cs | 83 +++++++++++++++++-- 1 file changed, 77 insertions(+), 6 deletions(-) diff --git a/Src/Newtonsoft.Json/Serialization/JsonContract.cs b/Src/Newtonsoft.Json/Serialization/JsonContract.cs index e376bc99..2a5c3f2e 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonContract.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonContract.cs @@ -24,6 +24,7 @@ #endregion using System; +using System.Collections.Generic; using System.Reflection; using System.Runtime.Serialization; using Newtonsoft.Json.Utilities; @@ -89,29 +90,85 @@ namespace Newtonsoft.Json.Serialization // checked for after passed in converters and attribute specified converters internal JsonConverter InternalConverter { get; set; } + /// + /// Gets or sets all methods called immediately after deserialization of the object. + /// + /// The methods called immediately after deserialization of the object. + public List OnDeserializedMethods { get; set; } + + /// + /// Gets or sets all methods called during deserialization of the object. + /// + /// The methods called during deserialization of the object. + public List OnDeserializingMethods { get; set; } + + /// + /// Gets or sets all methods called after serialization of the object graph. + /// + /// The methods called after serialization of the object graph. + public List OnSerializedMethods { get; set; } + + /// + /// Gets or sets all methods called before serialization of the object. + /// + /// The methods called before serialization of the object. + public List OnSerializingMethods { get; set; } + /// /// Gets or sets the method called immediately after deserialization of the object. /// /// The method called immediately after deserialization of the object. - public MethodInfo OnDeserialized { get; set; } + public MethodInfo OnDeserialized + { + get { return (OnDeserializedMethods != null && OnDeserializedMethods.Count > 0 ? OnDeserializedMethods[0] : null); } + set + { + OnDeserializedMethods = new List(); + OnDeserializedMethods.Add(value); + } + } /// /// Gets or sets the method called during deserialization of the object. /// /// The method called during deserialization of the object. - public MethodInfo OnDeserializing { get; set; } + public MethodInfo OnDeserializing + { + get { return (OnDeserializingMethods != null && OnDeserializingMethods.Count > 0 ? OnDeserializingMethods[0] : null); } + set + { + OnDeserializingMethods = new List(); + OnDeserializingMethods.Add(value); + } + } /// /// Gets or sets the method called after serialization of the object graph. /// /// The method called after serialization of the object graph. - public MethodInfo OnSerialized { get; set; } + public MethodInfo OnSerialized + { + get { return (OnSerializedMethods != null && OnSerializedMethods.Count > 0 ? OnSerializedMethods[0] : null); } + set + { + OnSerializedMethods = new List(); + OnSerializedMethods.Add(value); + } + } /// /// Gets or sets the method called before serialization of the object. /// /// The method called before serialization of the object. - public MethodInfo OnSerializing { get; set; } + public MethodInfo OnSerializing + { + get { return (OnSerializingMethods != null && OnSerializingMethods.Count > 0 ? OnSerializingMethods[0] : null); } + set + { + OnSerializingMethods = new List(); + OnSerializingMethods.Add(value); + } + } /// /// Gets or sets the default creator method used to create the object. @@ -125,11 +182,25 @@ namespace Newtonsoft.Json.Serialization /// true if the default object creator is non-public; otherwise, false. public bool DefaultCreatorNonPublic { get; set; } + /// + /// Gets or sets all method called when an error is thrown during the serialization of the object. + /// + /// The methods called when an error is thrown during the serialization of the object. + public List OnErrorMethods { get; set; } + /// /// Gets or sets the method called when an error is thrown during the serialization of the object. /// /// The method called when an error is thrown during the serialization of the object. - public MethodInfo OnError { get; set; } + public MethodInfo OnError + { + get { return (OnErrorMethods != null && OnErrorMethods.Count > 0 ? OnErrorMethods[0] : null); } + set + { + OnErrorMethods = new List(); + OnErrorMethods.Add(value); + } + } internal void InvokeOnSerializing(object o, StreamingContext context) { @@ -206,4 +277,4 @@ namespace Newtonsoft.Json.Serialization } } } -} \ No newline at end of file +} From 7cc5d306d14b10faec2fc0902a25522e747830f5 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Tue, 12 Feb 2013 01:51:44 -0800 Subject: [PATCH 2/6] -patch signatures in DefaultContractResolver to use arrays --- .../Serialization/DefaultContractResolver.cs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs b/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs index d1a3d8cb..e52245f8 100644 --- a/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs +++ b/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs @@ -550,11 +550,11 @@ namespace Newtonsoft.Json.Serialization if (t.BaseType() != null) ResolveCallbackMethods(contract, t.BaseType()); - MethodInfo onSerializing; - MethodInfo onSerialized; - MethodInfo onDeserializing; - MethodInfo onDeserialized; - MethodInfo onError; + List onSerializing; + List onSerialized; + List onDeserializing; + List onDeserialized; + List onError; GetCallbackMethodsForType(t, out onSerializing, out onSerialized, out onDeserializing, out onDeserialized, out onError); @@ -562,34 +562,34 @@ namespace Newtonsoft.Json.Serialization { #if NETFX_CORE if (!t.IsGenericType() || (t.GetGenericTypeDefinition() != typeof(ConcurrentDictionary<,>))) - contract.OnSerializing = onSerializing; + contract.OnSerializingMethods = onSerializing; #else - contract.OnSerializing = onSerializing; + contract.OnSerializingMethods = onSerializing; #endif } if (onSerialized != null) - contract.OnSerialized = onSerialized; + contract.OnSerializedMethods = onSerialized; if (onDeserializing != null) - contract.OnDeserializing = onDeserializing; + contract.OnDeserializingMethods = onDeserializing; if (onDeserialized != null) { // ConcurrentDictionary throws an error here so don't use its OnDeserialized - http://json.codeplex.com/discussions/257093 #if !(NET35 || NET20 || SILVERLIGHT || WINDOWS_PHONE || PORTABLE) if (!t.IsGenericType() || (t.GetGenericTypeDefinition() != typeof(ConcurrentDictionary<,>))) - contract.OnDeserialized = onDeserialized; + contract.OnDeserializedMethods = onDeserialized; #else - contract.OnDeserialized = onDeserialized; + contract.OnDeserializedMethods = onDeserialized; #endif } if (onError != null) - contract.OnError = onError; + contract.OnErrorMethods = onError; } - private void GetCallbackMethodsForType(Type type, out MethodInfo onSerializing, out MethodInfo onSerialized, out MethodInfo onDeserializing, out MethodInfo onDeserialized, out MethodInfo onError) + private void GetCallbackMethodsForType(Type type, out List onSerializing, out List onSerialized, out List onDeserializing, out List onDeserialized, out List onError) { onSerializing = null; onSerialized = null; From 2108d65a837156ea1868afaf9341ecf8c40e0e36 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Tue, 12 Feb 2013 10:58:11 -0800 Subject: [PATCH 3/6] -patch InvokeOn* to call all methods --- .../Serialization/JsonContract.cs | 39 ++++++++++++++----- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/Src/Newtonsoft.Json/Serialization/JsonContract.cs b/Src/Newtonsoft.Json/Serialization/JsonContract.cs index 2a5c3f2e..82f12dfe 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonContract.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonContract.cs @@ -204,32 +204,51 @@ namespace Newtonsoft.Json.Serialization internal void InvokeOnSerializing(object o, StreamingContext context) { - if (OnSerializing != null) - OnSerializing.Invoke(o, new object[] {context}); + if (OnSerializingMethods == null) return; + + foreach (MethodInfo mi in OnSerializingMethods) + { + mi.Invoke(o, new object[] {context}); + } } internal void InvokeOnSerialized(object o, StreamingContext context) { - if (OnSerialized != null) - OnSerialized.Invoke(o, new object[] {context}); + if (OnSerializedMethods == null) return; + + foreach (MethodInfo mi in OnSerializedMethods) + { + mi.Invoke(o, new object[] {context}); + } } internal void InvokeOnDeserializing(object o, StreamingContext context) { - if (OnDeserializing != null) - OnDeserializing.Invoke(o, new object[] {context}); + if (OnDeserializingMethods == null) return; + + foreach (MethodInfo mi in OnDeserializingMethods) + { + mi.Invoke(o, new object[] {context}); + } } internal void InvokeOnDeserialized(object o, StreamingContext context) { - if (OnDeserialized != null) - OnDeserialized.Invoke(o, new object[] {context}); + if (OnDeserializedMethods == null) return; + + foreach (MethodInfo mi in OnDeserializedMethods) + { + mi.Invoke(o, new object[] {context}); + } } internal void InvokeOnError(object o, StreamingContext context, ErrorContext errorContext) { - if (OnError != null) - OnError.Invoke(o, new object[] {context, errorContext}); + if (OnErrorMethods == null) return; + foreach (MethodInfo mi in OnErrorMethods) + { + mi.Invoke(o, new object[] {context, errorContext}); + } } internal JsonContract(Type underlyingType) From a1bfcde4451a67afd4a9b2b98ec1da830f17ae47 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Tue, 12 Feb 2013 11:14:25 -0800 Subject: [PATCH 4/6] -modify DefaultContractResolver to walk class hierarchy --- .../Serialization/DefaultContractResolver.cs | 95 +++++++++++++------ 1 file changed, 68 insertions(+), 27 deletions(-) diff --git a/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs b/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs index e52245f8..49e03762 100644 --- a/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs +++ b/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs @@ -597,39 +597,80 @@ namespace Newtonsoft.Json.Serialization onDeserialized = null; onError = null; - foreach (MethodInfo method in type.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)) + foreach (Type baseType in GetClassHierarchyForType(type)) { - // compact framework errors when getting parameters for a generic method - // lame, but generic methods should not be callbacks anyway - if (method.ContainsGenericParameters) - continue; + // while we allow more than one OnSerialized total, only one can be defined + // per class + MethodInfo currentOnSerializing = null; + MethodInfo currentOnSerialized = null; + MethodInfo currentOnDeserializing = null; + MethodInfo currentOnDeserialized = null; + MethodInfo currentOnError = null; - Type prevAttributeType = null; - ParameterInfo[] parameters = method.GetParameters(); + foreach (MethodInfo method in baseType.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)) + { + // compact framework errors when getting parameters for a generic method + // lame, but generic methods should not be callbacks anyway + if (method.ContainsGenericParameters) + continue; - if (IsValidCallback(method, parameters, typeof(OnSerializingAttribute), onSerializing, ref prevAttributeType)) - { - onSerializing = method; - } - if (IsValidCallback(method, parameters, typeof(OnSerializedAttribute), onSerialized, ref prevAttributeType)) - { - onSerialized = method; - } - if (IsValidCallback(method, parameters, typeof(OnDeserializingAttribute), onDeserializing, ref prevAttributeType)) - { - onDeserializing = method; - } - if (IsValidCallback(method, parameters, typeof(OnDeserializedAttribute), onDeserialized, ref prevAttributeType)) - { - onDeserialized = method; - } - if (IsValidCallback(method, parameters, typeof(OnErrorAttribute), onError, ref prevAttributeType)) - { - onError = method; + Type prevAttributeType = null; + ParameterInfo[] parameters = method.GetParameters(); + + if (IsValidCallback(method, parameters, typeof(OnSerializingAttribute), currentOnSerializing, ref prevAttributeType)) + { + onSerializing = onSerializing ?? new List(); + onSerializing.Add(method); + currentOnSerializing = method; + } + if (IsValidCallback(method, parameters, typeof(OnSerializedAttribute), currentOnSerialized, ref prevAttributeType)) + { + onSerialized = onSerialized ?? new List(); + onSerialized.Add(method); + currentOnSerialized = method; + } + if (IsValidCallback(method, parameters, typeof(OnDeserializingAttribute), currentOnDeserializing, ref prevAttributeType)) + { + onDeserializing = onDeserializing ?? new List(); + onDeserializing.Add(method); + currentOnDeserializing = method; + } + if (IsValidCallback(method, parameters, typeof(OnDeserializedAttribute), currentOnDeserialized, ref prevAttributeType)) + { + onDeserialized = onDeserialized ?? new List(); + onDeserialized.Add(method); + currentOnDeserialized = method; + } + if (IsValidCallback(method, parameters, typeof(OnErrorAttribute), currentOnError, ref prevAttributeType)) + { + onError = onError ?? new List(); + onError.Add(method); + currentOnError = method; + } } } } + private List GetClassHierarchyForType(Type type) + { + List ret = new List(); + + Type current = type; + while (current != null && current != typeof(object)) + { + ret.Add(current); +#if NETFX_CORE + current = current.BaseType(); +#else + current = current.BaseType; +#endif + } + + // Return the class list in order of simple => complex + ret.Reverse(); + return ret; + } + /// /// Creates a for the given type. /// @@ -1108,4 +1149,4 @@ namespace Newtonsoft.Json.Serialization return ResolvePropertyName(propertyName); } } -} \ No newline at end of file +} From 3384b65fb58d3e7d7575214819eea2d93d7c37e8 Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Tue, 12 Feb 2013 11:21:54 -0800 Subject: [PATCH 5/6] -patch use of JsonContract.OnXXXX to use OnXXXXMethods --- .../Serialization/JsonSerializerInternalReader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs index a91f60fc..307b5b2e 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonSerializerInternalReader.cs @@ -561,10 +561,10 @@ To fix this error either change the JSON to a {1} or change the deserialized typ if (id != null && isTemporaryListReference) throw JsonSerializationException.Create(reader, "Cannot preserve reference to array or readonly list: {0}.".FormatWith(CultureInfo.InvariantCulture, contract.UnderlyingType)); - if (contract.OnSerializing != null && isTemporaryListReference) + if (contract.OnSerializingMethods != null && isTemporaryListReference) throw JsonSerializationException.Create(reader, "Cannot call OnSerializing on an array or readonly list: {0}.".FormatWith(CultureInfo.InvariantCulture, contract.UnderlyingType)); - if (contract.OnError != null && isTemporaryListReference) + if (contract.OnErrorMethods != null && isTemporaryListReference) throw JsonSerializationException.Create(reader, "Cannot call OnError on an array or readonly list: {0}.".FormatWith(CultureInfo.InvariantCulture, contract.UnderlyingType)); if (!arrayContract.IsMultidimensionalArray) From 4bd932396da6d6364b27062d2e32dc570cf2f4ac Mon Sep 17 00:00:00 2001 From: Paul Betts Date: Tue, 12 Feb 2013 12:11:51 -0800 Subject: [PATCH 6/6] -add tests for base class object events --- .../SerializationEventAttributeTests.cs | 72 +++++++++++-------- .../SerializationEventTestObject.cs | 38 +++++++++- 2 files changed, 79 insertions(+), 31 deletions(-) diff --git a/Src/Newtonsoft.Json.Tests/Serialization/SerializationEventAttributeTests.cs b/Src/Newtonsoft.Json.Tests/Serialization/SerializationEventAttributeTests.cs index 87a9bf31..3c98b99f 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/SerializationEventAttributeTests.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/SerializationEventAttributeTests.cs @@ -48,41 +48,53 @@ namespace Newtonsoft.Json.Tests.Serialization [Test] public void ObjectEvents() { - SerializationEventTestObject obj = new SerializationEventTestObject(); + SerializationEventTestObject[] objs = new[] { new SerializationEventTestObject(), new DerivedSerializationEventTestObject() }; - Assert.AreEqual(11, obj.Member1); - Assert.AreEqual("Hello World!", obj.Member2); - Assert.AreEqual("This is a nonserialized value", obj.Member3); - Assert.AreEqual(null, obj.Member4); - Assert.AreEqual(null, obj.Member5); + foreach(SerializationEventTestObject current in objs) + { + SerializationEventTestObject obj = current; - string json = JsonConvert.SerializeObject(obj, Formatting.Indented); - Assert.AreEqual(@"{ - ""Member1"": 11, - ""Member2"": ""This value went into the data file during serialization."", - ""Member4"": null -}", json); + Assert.AreEqual(11, obj.Member1); + Assert.AreEqual("Hello World!", obj.Member2); + Assert.AreEqual("This is a nonserialized value", obj.Member3); + Assert.AreEqual(null, obj.Member4); + Assert.AreEqual(null, obj.Member5); - Assert.AreEqual(11, obj.Member1); - Assert.AreEqual("This value was reset after serialization.", obj.Member2); - Assert.AreEqual("This is a nonserialized value", obj.Member3); - Assert.AreEqual(null, obj.Member4); - Assert.AreEqual("Error message for member Member6 = Error getting value from 'Member6' on 'Newtonsoft.Json.Tests.TestObjects.SerializationEventTestObject'.", obj.Member5); + string json = JsonConvert.SerializeObject(obj, Formatting.Indented); + Assert.AreEqual(@"{ + ""Member1"": 11, + ""Member2"": ""This value went into the data file during serialization."", + ""Member4"": null + }", json); - JObject o = JObject.Parse(@"{ - ""Member1"": 11, - ""Member2"": ""This value went into the data file during serialization."", - ""Member4"": null -}"); - o["Member6"] = "Dummy text for error"; + Assert.AreEqual(11, obj.Member1); + Assert.AreEqual("This value was reset after serialization.", obj.Member2); + Assert.AreEqual("This is a nonserialized value", obj.Member3); + Assert.AreEqual(null, obj.Member4); - obj = JsonConvert.DeserializeObject(o.ToString()); + string expectedError = String.Format("Error message for member Member6 = Error getting value from 'Member6' on '{0}'.", obj.GetType().FullName); + Assert.AreEqual(expectedError, obj.Member5); - Assert.AreEqual(11, obj.Member1); - Assert.AreEqual("This value went into the data file during serialization.", obj.Member2); - Assert.AreEqual("This value was set during deserialization", obj.Member3); - Assert.AreEqual("This value was set after deserialization.", obj.Member4); - Assert.AreEqual("Error message for member Member6 = Error setting value to 'Member6' on 'Newtonsoft.Json.Tests.TestObjects.SerializationEventTestObject'.", obj.Member5); + JObject o = JObject.Parse(@"{ + ""Member1"": 11, + ""Member2"": ""This value went into the data file during serialization."", + ""Member4"": null + }"); + o["Member6"] = "Dummy text for error"; + + obj = (SerializationEventTestObject) JsonConvert.DeserializeObject(o.ToString(), obj.GetType()); + + Assert.AreEqual(11, obj.Member1); + Assert.AreEqual("This value went into the data file during serialization.", obj.Member2); + Assert.AreEqual("This value was set during deserialization", obj.Member3); + Assert.AreEqual("This value was set after deserialization.", obj.Member4); + + expectedError = String.Format("Error message for member Member6 = Error setting value to 'Member6' on '{0}'.", obj.GetType()); + Assert.AreEqual(expectedError, obj.Member5); + + DerivedSerializationEventTestObject derivedObj = obj as DerivedSerializationEventTestObject; + if (derivedObj != null) Assert.AreEqual("This value was set after deserialization.", derivedObj.Member7); + } } [Test] @@ -335,4 +347,4 @@ namespace Newtonsoft.Json.Tests.Serialization } } } -} \ No newline at end of file +} diff --git a/Src/Newtonsoft.Json.Tests/TestObjects/SerializationEventTestObject.cs b/Src/Newtonsoft.Json.Tests/TestObjects/SerializationEventTestObject.cs index 3965447f..b77e05b4 100644 --- a/Src/Newtonsoft.Json.Tests/TestObjects/SerializationEventTestObject.cs +++ b/Src/Newtonsoft.Json.Tests/TestObjects/SerializationEventTestObject.cs @@ -96,4 +96,40 @@ namespace Newtonsoft.Json.Tests.TestObjects errorContext.Handled = true; } } -} \ No newline at end of file + + public class DerivedSerializationEventTestObject : SerializationEventTestObject + { + // This field is set to null, but populated after deserialization, only + // in the derived class + [JsonIgnore] + public string Member7 { get; set; } + + // These empty methods exist to make sure we're not covering up the base + // methods + [OnSerializing] + internal void OnDerivedSerializingMethod(StreamingContext context) + { + } + + [OnSerialized] + internal void OnDerivedSerializedMethod(StreamingContext context) + { + } + + [OnDeserializing] + internal void OnDerivedDeserializingMethod(StreamingContext context) + { + } + + [OnDeserialized] + internal void OnDerivedDeserializedMethod(StreamingContext context) + { + Member7 = "This value was set after deserialization."; + } + + [OnError] + internal void OnDerivedErrorMethod(StreamingContext context, ErrorContext errorContext) + { + } + } +}