C# Deserialize an object which is derived and has references

北城余情 提交于 2019-12-29 09:13:08

问题


I have a object of Type Node. Node.cs

The serialization works when I make the call as following:

var nodeSer = JsonConvert.SerializeObject(mynode, Formatting.Indented,
   new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects });

My problem is that the following call does not work.

var n = JsonConvert.DeserializeObject<Node>(nodeSer,
                new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects, TypeNameHandling = TypeNameHandling.Auto });

The call causes the following error:

Newtonsoft.Json.JsonSerializationException: "ISerializable type 'System.Action' does not have a valid constructor. To correctly implement ISerializable a constructor that takes SerializationInfo and StreamingContext parameters should be present. Path 'Size.ValueChanged', line 35, position 5."

How do I have to design the the deserialization call?


回答1:


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.



来源:https://stackoverflow.com/questions/42599259/c-sharp-deserialize-an-object-which-is-derived-and-has-references

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!