Json.NET does not serialize events so the public event PropertyChangedEventHandler PropertyChanged
in the PropertyChangedBase
base type of the HousePlan
repository should not cause problems during (de)serialization.
However, at least one of the types in that repository has a System.Action
delegate rather than an event to handle when a value changes, specifically BindablePoint
:
public class BindablePoint: PropertyChangedBase
{
public double X
{
get { return Value.X; }
set { Value = new Point(value, Value.Y); }
}
public double Y
{
get { return Value.Y; }
set { Value = new Point( Value.X, value); }
}
private Point _value;
public Point Value
{
get { return _value; }
set
{
_value = value;
OnPropertyChanged("Value");
OnPropertyChanged("X");
OnPropertyChanged("Y");
if (ValueChanged != null)
ValueChanged();
}
}
// This property is causing problems for Json.NET
public Action ValueChanged;
}
It's not clear why a delegate rather than an event is used for this purpose, however System.Action
cannot be deserialized by Json.NET. Indeed, serializing and deserializing these delegates makes no sense since they are assigned in the constructor for Node
:
public class Node: DiagramObject
{
public Node()
{
Size.ValueChanged = RecalculateSnaps;
Location.ValueChanged = RecalculateSnaps;
}
One simple solution is to mark these properties with [JsonIgnore]
[JsonIgnore]
public Action ValueChanged;
A second simple solution would be to replace the delegate with a proper event, which Json.NET will now ignore:
public event EventHandler ValueChanged;
If for whatever reason you cannot change these types, you can create a custom ContractResolver
that automatically ignores all delegate type properties:
public class IgnorePropertiesOfTypeContractResolver<T> : IgnorePropertiesOfTypeContractResolver
{
// As of 7.0.1, Json.NET suggests using a static instance for "stateless" contract resolvers, for performance reasons.
// http://www.newtonsoft.com/json/help/html/ContractResolver.htm
// http://www.newtonsoft.com/json/help/html/M_Newtonsoft_Json_Serialization_DefaultContractResolver__ctor_1.htm
// "Use the parameterless constructor and cache instances of the contract resolver within your application for optimal performance."
static IgnorePropertiesOfTypeContractResolver<T> instance;
static IgnorePropertiesOfTypeContractResolver() { instance = new IgnorePropertiesOfTypeContractResolver<T>(); }
public static IgnorePropertiesOfTypeContractResolver<T> Instance { get { return instance; } }
public IgnorePropertiesOfTypeContractResolver() : base(new[] { typeof(T) }) { }
}
/// <summary>
/// Contract resolver to ignore properties of any number of given types.
/// </summary>
public class IgnorePropertiesOfTypeContractResolver : DefaultContractResolver
{
readonly HashSet<Type> toIgnore;
public IgnorePropertiesOfTypeContractResolver(IEnumerable<Type> toIgnore)
{
if (toIgnore == null)
throw new ArgumentNullException();
this.toIgnore = new HashSet<Type>(toIgnore);
}
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (property.PropertyType.BaseTypesAndSelf().Any(t => toIgnore.Contains(t)))
{
property.Ignored = true;
}
return property;
}
}
public static class TypeExtensions
{
public static IEnumerable<Type> BaseTypesAndSelf(this Type type)
{
while (type != null)
{
yield return type;
type = type.BaseType;
}
}
}
Now serialize with the following settings:
var settings = new JsonSerializerSettings
{
PreserveReferencesHandling = PreserveReferencesHandling.Objects,
ContractResolver = IgnorePropertiesOfTypeContractResolver<System.Delegate>.Instance,
};
The ValueChanged
property will no longer be serialized or deserialized.