INotifyPropertyChanged and Deserialization
Asked Answered
R

1

6

Given:

public class MyClass : INotifyPropertyChanged
{
    public List<string> _TestFire = new List<string>();

    string _StringProp;
    public string StringProp
    {
        get
        {
            return _StringProp;
        }
        set
        {
            if (_StringProp != value)
            {
                _StringProp = value;
                RaisePropertyChanged("StringProp");
                _TestFire.Add("fired " + DateTime.Now);
            }
        }
    }
}

When you serialize and then deserialize this class, it fires the RaisePropertyChanged event, which is undesirable. Is it possible to prevent this event from being fired when the class gets deserialized?

var MyclassInstance = new MyClass() { StringProp = "None" };

MyclassInstance._TestFire.Clear(); // Clear property change history

var serobj = JsonConvert.SerializeObject();

var newitem = JsonConvert.DeserializeObject<MyClass>(serobj);

// newitem._TestFire.Count == 1, the set method was executed

Is there a way to get a bool value if the class is being deserialized? So then I could do:

        set
        {
            if (_StringProp != value)
            {
                _StringProp = value;

                if (!Deserializing)
                {
                    RaisePropertyChanged("StringProp");
                    _TestFire.Add("fired " + DateTime.Now);
                }
            }
        }
Rank answered 11/4, 2019 at 22:7 Comment(0)
P
7

Yes, you can do what you want by implementing the OnDeserializing and OnDeserialized serialization callback methods in your class. In the OnDeserializing method, set your private _isDeserializing variable to true, and in OnDeserialized set it back to false. I would recommend doing the _isDeserializing check inside the RaisePropertyChanged method so you don't have duplicate code inside every property.

So you would end up with something like this:

public class MyClass : INotifyPropertyChanged
{
    public List<string> _TestFire = new List<string>();

    string _StringProp;
    public string StringProp
    {
        get
        {
            return _StringProp;
        }
        set
        {
            if (_StringProp != value)
            {
                _StringProp = value;
                RaisePropertyChanged("StringProp");
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected void RaisePropertyChanged(string propertyName)
    {
        // don't raise the event if the property is being changed due to deserialization    
        if (_isDeserializing) return;

        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
        _TestFire.Add(propertyName + " was fired " + DateTime.Now);
    }

    bool _isDeserializing = false;

    [OnDeserializing]
    internal void OnDeserializingMethod(StreamingContext context)
    {
        _isDeserializing = true;
    }

    [OnDeserialized]
    internal void OnDeserializedMethod(StreamingContext context)
    {
        _isDeserializing = false;
    }
}

Working demo: https://dotnetfiddle.net/QkOcF4


Another way you could solve this problem is to mark your public properties with [JsonIgnore] and then mark the corresponding backing fields with [JsonProperty], specifying the property name to use in the JSON. This would allow Json.Net to set the backing fields directly and not execute any of the mutator logic.

public class MyClass : INotifyPropertyChanged
{
    public List<string> _TestFire = new List<string>();

    [JsonProperty("StringProp")]
    string _StringProp;

    [JsonIgnore]
    public string StringProp
    {
        get
        {
            return _StringProp;
        }
        set
        {
            if (_StringProp != value)
            {
                _StringProp = value;
                RaisePropertyChanged("StringProp");
                _TestFire.Add("StringProp was fired " + DateTime.Now);
            }
        }
    }

    ...
}

Working demo: https://dotnetfiddle.net/jc7wDu

Pogonia answered 11/4, 2019 at 23:59 Comment(5)
That's extremely helpful. Thank you!Rank
Agree, can't believe I've been working with JSON for so long, and not hit this problem. @BrianRogers thanksDuval
I liked the idea of putting JsonProperty attribute on the backing field, this is brilliance. Just for double checking as this question is about deserialization. Is it ok to use this trick on serialization also.Thane
@JawadSabir Yes, it should work. Give it a try.Pogonia
Useful for sure. I had to modify the first idea two ways: I could see via the default constructor on my class was running before the OnDeserializing method. I know that's not supposed to happen, but it is. So set _isDeserializing=true at the beginning of the default .ctor too. Second: needed to set [JsonIgnore] on the field _isDeserializing.Gothard

© 2022 - 2024 — McMap. All rights reserved.