Wouter
Wouter

Reputation: 558

IReactiveDerivedList broken after deserialization

When I create a ViewModel with a ReactiveList and an IReactiveDerivedList and then serialize and deserialize it using Json.net, the derived list seems to be broken. What am I doing wrong?

The viewmodel:

[DataContract]
public class TestViewModel : ReactiveObject
{
    [DataMember]
    public ReactiveList<int> List { get; } = new ReactiveList<int>();

    public IReactiveDerivedList<int> DerivedList { get; }

    public TestViewModel()
    {
        //DerivedList contains all elements of List that are greater than 0.
        DerivedList = List.CreateDerivedCollection(v => v, v => v > 0);
    }
}

The serialization test:

private void Example()
{
    TestViewModel vm = new TestViewModel();
    vm.List.Add(0);
    vm.List.Add(1);

    //vm.DerivedList now has 1 item

    string json = JsonConvert.SerializeObject(vm);
    TestViewModel clone = JsonConvert.DeserializeObject<TestViewModel>(json);

    //vm.DerivedList now has 1 item
    //clone.DerivedList now has 1 item

    vm.List.Add(1);
    clone.List.Add(1);

    //vm.DerivedList now has 2 items
    //clone.DerivedList now has 1 item
}

Upvotes: 1

Views: 139

Answers (1)

Eugene Pawlik
Eugene Pawlik

Reputation: 1190

I think the issue here is that when the ViewModel is deserialized, the ReactiveList used as the source for the derived list is also deserialized, which is where the problem lies.

When your TestViewModel is deserialized, the order of events goes something like this:

  • The ReactiveList constructor runs. This sets up the observable backing fields.
  • The TestViewModel constructor runs. This sets up the derived collection, which subscribes to the observables on the ReactiveList.
  • The ReactiveList "OnDeserialized" callback runs. This sets up the observable backing fields again, overwriting their previous values.

The last step is where the issue occurs. The derived collection is subscribed to the old observables, not the ones that are now being used by the ReactiveList. One way to fix it would be to give the derived list property a private setter, implement a deserialization callback, and create the derived list in that callback.

[DataContract]
public class TestViewModel : ReactiveObject
{
    [DataMember]
    public ReactiveList<int> List { get; } = new ReactiveList<int>();

    public IReactiveDerivedList<int> DerivedList { get; private set; }

    public TestViewModel()
    {
        SetupRx();
    }

    [OnDeserialized]
    internal void OnDeserialized(StreamingContext context)
    {
        SetupRx();
    }

    private void SetupRx()
    {
        DerivedList?.Dispose();
        DerivedList = List.CreateDerivedCollection(v => v, v => v > 0);
    }
}

Upvotes: 2

Related Questions