Merge pull request #56 from xpaulbettsx/call-base-class-ondeserialized

Call base classes' "On[De]Serializ[ing|ed]" as well as derived class
This commit is contained in:
James Newton-King
2013-02-14 01:13:49 -08:00
5 changed files with 268 additions and 89 deletions
@@ -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<SerializationEventTestObject>(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
}
}
}
}
}
@@ -96,4 +96,40 @@ namespace Newtonsoft.Json.Tests.TestObjects
errorContext.Handled = true;
}
}
}
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)
{
}
}
}
@@ -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<MethodInfo> onSerializing;
List<MethodInfo> onSerialized;
List<MethodInfo> onDeserializing;
List<MethodInfo> onDeserialized;
List<MethodInfo> 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<MethodInfo> onSerializing, out List<MethodInfo> onSerialized, out List<MethodInfo> onDeserializing, out List<MethodInfo> onDeserialized, out List<MethodInfo> onError)
{
onSerializing = null;
onSerialized = null;
@@ -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<MethodInfo>();
onSerializing.Add(method);
currentOnSerializing = method;
}
if (IsValidCallback(method, parameters, typeof(OnSerializedAttribute), currentOnSerialized, ref prevAttributeType))
{
onSerialized = onSerialized ?? new List<MethodInfo>();
onSerialized.Add(method);
currentOnSerialized = method;
}
if (IsValidCallback(method, parameters, typeof(OnDeserializingAttribute), currentOnDeserializing, ref prevAttributeType))
{
onDeserializing = onDeserializing ?? new List<MethodInfo>();
onDeserializing.Add(method);
currentOnDeserializing = method;
}
if (IsValidCallback(method, parameters, typeof(OnDeserializedAttribute), currentOnDeserialized, ref prevAttributeType))
{
onDeserialized = onDeserialized ?? new List<MethodInfo>();
onDeserialized.Add(method);
currentOnDeserialized = method;
}
if (IsValidCallback(method, parameters, typeof(OnErrorAttribute), currentOnError, ref prevAttributeType))
{
onError = onError ?? new List<MethodInfo>();
onError.Add(method);
currentOnError = method;
}
}
}
}
private List<Type> GetClassHierarchyForType(Type type)
{
List<Type> ret = new List<Type>();
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;
}
/// <summary>
/// Creates a <see cref="JsonDictionaryContract"/> for the given type.
/// </summary>
@@ -1108,4 +1149,4 @@ namespace Newtonsoft.Json.Serialization
return ResolvePropertyName(propertyName);
}
}
}
}
+106 -16
View File
@@ -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; }
/// <summary>
/// Gets or sets all methods called immediately after deserialization of the object.
/// </summary>
/// <value>The methods called immediately after deserialization of the object.</value>
public List<MethodInfo> OnDeserializedMethods { get; set; }
/// <summary>
/// Gets or sets all methods called during deserialization of the object.
/// </summary>
/// <value>The methods called during deserialization of the object.</value>
public List<MethodInfo> OnDeserializingMethods { get; set; }
/// <summary>
/// Gets or sets all methods called after serialization of the object graph.
/// </summary>
/// <value>The methods called after serialization of the object graph.</value>
public List<MethodInfo> OnSerializedMethods { get; set; }
/// <summary>
/// Gets or sets all methods called before serialization of the object.
/// </summary>
/// <value>The methods called before serialization of the object.</value>
public List<MethodInfo> OnSerializingMethods { get; set; }
/// <summary>
/// Gets or sets the method called immediately after deserialization of the object.
/// </summary>
/// <value>The method called immediately after deserialization of the object.</value>
public MethodInfo OnDeserialized { get; set; }
public MethodInfo OnDeserialized
{
get { return (OnDeserializedMethods != null && OnDeserializedMethods.Count > 0 ? OnDeserializedMethods[0] : null); }
set
{
OnDeserializedMethods = new List<MethodInfo>();
OnDeserializedMethods.Add(value);
}
}
/// <summary>
/// Gets or sets the method called during deserialization of the object.
/// </summary>
/// <value>The method called during deserialization of the object.</value>
public MethodInfo OnDeserializing { get; set; }
public MethodInfo OnDeserializing
{
get { return (OnDeserializingMethods != null && OnDeserializingMethods.Count > 0 ? OnDeserializingMethods[0] : null); }
set
{
OnDeserializingMethods = new List<MethodInfo>();
OnDeserializingMethods.Add(value);
}
}
/// <summary>
/// Gets or sets the method called after serialization of the object graph.
/// </summary>
/// <value>The method called after serialization of the object graph.</value>
public MethodInfo OnSerialized { get; set; }
public MethodInfo OnSerialized
{
get { return (OnSerializedMethods != null && OnSerializedMethods.Count > 0 ? OnSerializedMethods[0] : null); }
set
{
OnSerializedMethods = new List<MethodInfo>();
OnSerializedMethods.Add(value);
}
}
/// <summary>
/// Gets or sets the method called before serialization of the object.
/// </summary>
/// <value>The method called before serialization of the object.</value>
public MethodInfo OnSerializing { get; set; }
public MethodInfo OnSerializing
{
get { return (OnSerializingMethods != null && OnSerializingMethods.Count > 0 ? OnSerializingMethods[0] : null); }
set
{
OnSerializingMethods = new List<MethodInfo>();
OnSerializingMethods.Add(value);
}
}
/// <summary>
/// Gets or sets the default creator method used to create the object.
@@ -125,40 +182,73 @@ namespace Newtonsoft.Json.Serialization
/// <value><c>true</c> if the default object creator is non-public; otherwise, <c>false</c>.</value>
public bool DefaultCreatorNonPublic { get; set; }
/// <summary>
/// Gets or sets all method called when an error is thrown during the serialization of the object.
/// </summary>
/// <value>The methods called when an error is thrown during the serialization of the object.</value>
public List<MethodInfo> OnErrorMethods { get; set; }
/// <summary>
/// Gets or sets the method called when an error is thrown during the serialization of the object.
/// </summary>
/// <value>The method called when an error is thrown during the serialization of the object.</value>
public MethodInfo OnError { get; set; }
public MethodInfo OnError
{
get { return (OnErrorMethods != null && OnErrorMethods.Count > 0 ? OnErrorMethods[0] : null); }
set
{
OnErrorMethods = new List<MethodInfo>();
OnErrorMethods.Add(value);
}
}
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)
@@ -206,4 +296,4 @@ namespace Newtonsoft.Json.Serialization
}
}
}
}
}
@@ -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)