1

I have an OrderedDictionary with int keys and System.Drawing.Rectangle values. JSON.NET won't serialize the OrderedDictionary...it returns an empty object. I wrote a custom converter, but I wondered if there was an easier way. Thinking that JSON.NET might use the presence of a typed enumerator as the trigger to use its built-in code for serializing and deserializing a Dictionary<TKey, TValue> I tried this:

class Program
{
    static void Main(string[] args)
    {
        var test = new OrderedDictionary<int, Rectangle>();
        test.Add(1, new Rectangle(0, 0, 50, 50));
        test.Add(42, new Rectangle(1, 1, 1, 1));

        string s = JsonConvert.SerializeObject(test);
        var deserialized = JsonConvert.DeserializeObject<OrderedDictionary<int, Rectangle>>(s);

        var someRect = deserialized[(object)1]; // someRect is null
        var someOtherRect = (Rectangle)deserialized["1"]; // InvalidCastException
    }
}

public class OrderedDictionary<TKey, TValue> : OrderedDictionary, IEnumerable<KeyValuePair<TKey, TValue>>
{
    IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
    {
        foreach (TKey key in Keys)
        {
            yield return new KeyValuePair<TKey, TValue>(key, (TValue)this[key]);
        }
    }
}

Serialization works perfectly. However, when I deserialize, the keys in the dictionary become strings and the Rectangles are JObjects that can't be cast to Rectangle. Is there something I can add to my OrderedDictionary<> class that will allow for proper deserialization with JSON.NET? Thanks.

John Riehl
  • 1,270
  • 1
  • 11
  • 22
  • Which OrderedDictionary?, one in `System.Collections.Specialized` ? – Eser Mar 26 '16 at 18:47
  • Yes. Since I have to use 15 characters, I will also mention that I read [this post](http://stackoverflow.com/questions/2629027/no-generic-implementation-of-ordereddictionary) and am contemplating one of the fine implementations there, but fear the same deserialization behavior. – John Riehl Mar 26 '16 at 18:51
  • It's possible that JSON.NET has no support for this yet. You can post this inquiry on https://github.com/JamesNK/Newtonsoft.Json – John Ephraim Tugado Mar 27 '16 at 02:07
  • @JohnEphraimTugado Thanks for the advice, posted [here](https://github.com/JamesNK/Newtonsoft.Json/issues/859) – John Riehl Mar 27 '16 at 15:41

1 Answers1

2

Your problem is that, although you've added an enumerator, things like the indexer cannot be overridden. So what you're getting is the default implementation of the non-generic OrderedDictionary which doesn't give you a typed result.

So, instead of inheriting, you need a facade that fully implements the generic interface.

You'll need to verify my class (I just made the test work). I've also cheated with the Keys and Values properties (they're not often used) and some of the other ICollection methods. Just lazy :)

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Drawing;
using Newtonsoft.Json;
using Xunit;

namespace XUnitTestProject1
{
    public class UnitTest1
    {
        [Fact]
        public void TestJsonRectange()
        {
            var test = new OrderedDictionary<int, Rectangle>();
            test.Add(1, new Rectangle(0, 0, 50, 50));
            test.Add(42, new Rectangle(1, 1, 1, 1));
            string json = JsonConvert.SerializeObject(test);

            var deserialized = JsonConvert.DeserializeObject<OrderedDictionary<int, Rectangle>>(json);

            object someRect = deserialized[1];
            Assert.NotNull(someRect);
            Assert.True(someRect is Rectangle);
        }
        [Fact]
        public void TestJsonString()
        {
            var test = new OrderedDictionary<string, string>();
            test.Add("1", "11");
            test.Add("42", "4242");
            string json = JsonConvert.SerializeObject(test);

            var deserialized = JsonConvert.DeserializeObject<OrderedDictionary<string, string>>(json);

            object something = deserialized["1"];
            Assert.NotNull(something);
            Assert.True(something is string);
        }

        public class OrderedDictionary<TKey, TValue> : IDictionary<TKey, TValue>
        {
            private readonly OrderedDictionary dic = new OrderedDictionary();

            public TValue this[TKey key] { get { return (TValue)dic[key]; } set { dic[key] = value; } }

            public void Add(KeyValuePair<TKey, TValue> item)
            {
                dic.Add(item.Key, item.Value);
            }
            public void Add(TKey key, TValue value)
            {
                dic.Add(key, value);
            }

            public void Clear() { dic.Clear(); }


            public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) { }


            public int Count { get { return dic.Count; } }
            public bool IsReadOnly { get { return false; } }

            public bool Contains(TKey key) { return dic.Contains(key); }
            public bool ContainsKey(TKey key) { return dic.Contains(key); }

            public bool Remove(TKey key) { dic.Remove(key); return true; }

            public bool TryGetValue(TKey key, out TValue value) { value = default(TValue); return false; }

            bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
            {
                throw new NotImplementedException();
            }
            bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item) { return false; }

            public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
            {
                foreach (DictionaryEntry entry in dic)
                    yield return new KeyValuePair<TKey, TValue>((TKey)entry.Key, (TValue)entry.Value);
            }

            IEnumerator IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }

            private static readonly TKey[] keys = new TKey[0];
            private static readonly TValue[] values = new TValue[0];

            ICollection<TKey> IDictionary<TKey, TValue>.Keys { get { return keys; } }
            ICollection<TValue> IDictionary<TKey, TValue>.Values { get { return values; } }
        }
    }
}
AndyPook
  • 2,762
  • 20
  • 23
  • 1
    Unfortunately, `SortedDictionary` doesn't have the behavior I want. It sorts keys...I'm looking to preserve the order of insertion and removal. – John Riehl Mar 27 '16 at 14:55
  • This did not work for me with `OrderedDictionary`, deserializing left me with an empty collection. – Adam Nemitoff Oct 24 '19 at 15:49
  • @AdamNemitoff I think maybe that json.net has changed how it adds values to dictionaries. It now seems to use the indexer. I've updated the answer to add an impl for that. The tests now pass – AndyPook Oct 25 '19 at 18:13