6

I created a custom List class that maintains a set of item ids for performance reasons:

public class MyCustomList : List<ItemWithID>
{
    private HashSet<int> itemIDs = new HashSet<int>();

    public MyCustomList()
    {
    }

    [JsonConstructor]
    public MyCustomList(IEnumerable<ItemWithID> collection)
        : base(collection)
    {
        itemIDs = new HashSet<int>(this.Select(i => i.ID));
    }

    public new void Add(ItemWithID item)
    {
        base.Add(item);
        itemIDs.Add(item.ID);
    }

    public new bool Remove(ItemWithID   item)
    {
        var removed = base.Remove(item);
        if (removed)
        {
            itemIDs.Remove(item.ID);
        }
        return removed;
    }

    public bool ContainsID(int id)
    {
        return itemIDs.Contains(id);
    }
}

I want to deserialize this List from a simply JSON array e.g.:

JsonConvert.DeserializeObject<MyCustomList>("[{ID:8},{ID:9}]");

this will cause JSON.NET to call only the empty constructor, so my itemIDs list remains empty. Also the Add method is not called.

How does JSON.NET add the items to the list so I can add logic at that place.

(this is about deserialization without properties that should be persistent in the json string, so the suggested duplicate question has nothing to do with this one)

jansepke
  • 1,933
  • 2
  • 18
  • 30
  • 1
    not a duplicate (see updated question) – jansepke Nov 03 '15 at 11:17
  • 1
    Your problem isn't with JSON deserialization, your `MyCustomList` needs to derive from `IList` if you want to be able to *override* the `Add` method. See [THIS](http://stackoverflow.com/q/580202/2140173) for details. –  Nov 03 '15 at 11:41
  • @Meehow thanks now I understand the difference between new and override. Make this an answer and I will accept it. – jansepke Nov 03 '15 at 12:00
  • @JanS no problem, [answer added](http://stackoverflow.com/a/33498924/2140173). –  Nov 03 '15 at 12:16
  • 1
    Dear @jan-s, did not you like my answer which contained the actual working code since you marked as answer another one posted 45 minutes later? – Oguz Ozgul Nov 03 '15 at 12:34
  • Oh I see now. You promised :) – Oguz Ozgul Nov 03 '15 at 12:35
  • 1
    @OguzOzgul yeah your answer is a solution, but I was missing an explanation. – jansepke Nov 03 '15 at 12:38
  • You are right. Thanks – Oguz Ozgul Nov 03 '15 at 12:42
  • @OguzOzgul add first: yes (and gave you an upvote) but now I created my own ObservableList class that could be easily hooked in with a OnAdd and OnRemove callback – jansepke Nov 03 '15 at 12:57
  • Thanks. I'm also upvoting your question. Many would struggle with this at some point.. – Oguz Ozgul Nov 03 '15 at 13:00

3 Answers3

4

Solution:

public class MyCustomList : IList<ItemWithID>
{
    private HashSet<int> itemIDs = new HashSet<int>();
    private List<ItemWithID> actualList = new List<ItemWithID>();

    public void Add(ItemWithID item)
    {
        actualList.Add(item);
        itemIDs.Add(item.ID);
    }

    public bool Remove(ItemWithID item)
    {
        var removed = actualList.Remove(item);
        if (removed)
        {
            itemIDs.Remove(item.ID);
        }
        return removed;
    }

    public bool ContainsID(int id)
    {
        return itemIDs.Contains(id);
    }

    public int IndexOf(ItemWithID item)
    {
        return actualList.IndexOf(item);
    }

    public void Insert(int index, ItemWithID item)
    {
        actualList.Insert(index, item);
        itemIDs.Add(item.ID);
    }

    public void RemoveAt(int index)
    {
        itemIDs.Remove(actualList[index].ID);
        actualList.RemoveAt(index);

    }

    public ItemWithID this[int index]
    {
        get
        {
            return actualList[index];
        }
        set
        {
            actualList[index] = value;
            if (!itemIDs.Contains(value.ID))
            {
                itemIDs.Add(value.ID);
            }
        }
    }

    public void Clear()
    {
        actualList.Clear();
        itemIDs.Clear();
    }

    public bool Contains(ItemWithID item)
    {
        return actualList.Contains(item);
    }

    public void CopyTo(ItemWithID[] array, int arrayIndex)
    {
        actualList.CopyTo(array, arrayIndex);
    }

    public int Count
    {
        get { return actualList.Count; }
    }

    public bool IsReadOnly
    {
        get { return false; }
    }

    public IEnumerator<ItemWithID> GetEnumerator()
    {
        return actualList.GetEnumerator();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}
Oguz Ozgul
  • 6,809
  • 1
  • 14
  • 26
3

You could deserialize to the form the constructor expects, then call that yourself.

var collection = JsonConvert.DeserializeObject<ItemID[]>("[{ID:8},{ID:9}]");

var aCustomList = new MyCustomList(collection);
JonMac1374
  • 466
  • 2
  • 7
  • May I ask how is this any different from the current implementation besides the fact that it's split into 2 lines? –  Nov 03 '15 at 11:36
  • The only drawback to this is when the MyCustomList is deserialized by another party (WebAPI for instance) the item id list will again be empty. – Oguz Ozgul Nov 03 '15 at 11:36
  • 1
    @Meehow the difference is the type that JsonConvert deserializes to. Additionally, the desired constructor is then called using the result of that. In the original code the empty constructor is called. – JonMac1374 Nov 03 '15 at 11:51
  • @OguzOzgul exactly thats the problem. the deserilization example is simplified and not the real use case – jansepke Nov 03 '15 at 11:52
1

Your problem isn't with JSON deserialization, your MyCustomList class needs to derive from IList if you want to be able to override the Add method. See THIS for details.

Community
  • 1
  • 1