Deserialize ConcurrentQueue? Exception: Cannot call OnSerializing...

Feb 27, 2014 at 2:05 AM
Edited Feb 27, 2014 at 2:38 AM
Is it possible to serialize a class such that it preserves its use of ConcurrentQueue on deserialize?

I'm getting the below exception:
Newtonsoft.Json.JsonSerializationException : Cannot call OnSerializing on an array or readonly list, or list created from a non-default constructor: System.Collections.Concurrent.ConcurrentQueue`1[ConcurrentQueueJsonTests.CheckerItem]. Path '_queue', line 2, position 14.
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateList(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, Object existingValue, String id)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue(JsonProperty property, JsonConverter propertyConverter, JsonContainerContract containerContract, JsonProperty containerProperty, JsonReader reader, Object target)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)
at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent)
at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType)
at Newtonsoft.Json.JsonConvert.DeserializeObject(String value, Type type, JsonSerializerSettings settings)
at Newtonsoft.Json.JsonConvert.DeserializeObject(String value, JsonSerializerSettings settings)
at ConcurrentQueueJsonTests.Checker.Load(String filename) in Checker.cs: line 29
at ConcurrentQueueJsonTests.ConcurrentQueueTests.Foo() in ConcurrentQueueTests.cs: line 18
I've tried a few variations of the below code but this conveys the attempt:

Main class sample:
class Checker
    {
        // removed readonly for Json.net, also tried public property
        [JsonProperty] 
        private ConcurrentQueue<CheckerItem> _queue = new ConcurrentQueue<CheckerItem>();
        
        public void Enqueue(string filename)
        {
            var item = new CheckerItem(new FileInfo(filename));
            _queue.Enqueue(item);
        }
        
        public void Save(string filename)
        {
            var options = new JsonSerializerSettings {Formatting = Formatting.Indented};
            var json = JsonConvert.SerializeObject(this, options);
            File.WriteAllText(filename, json);
        }

        public static Checker Load(string filename)
        {
            var json = File.ReadAllText(filename);
            var checker = JsonConvert.DeserializeObject<Checker>(json);
            return checker;
        }
    }
Item sample:
internal class CheckerItem
    {
        public CheckerItem(FileSystemInfo file)
            : this()
        {
            this.ExistedOriginally = file.Exists;
            this.Filename = file.FullName;
            this.FileWriteTime = file.LastWriteTime;
        }

        public CheckerItem() { }

        public DateTime FileWriteTime { get; set; }  // removed private setter for serialize
        public string Filename { get; set; } // removed private setter for serialize
        public bool ExistedOriginally { get; set; } // removed private setter for serialize
    }
Test sample:
[TestFixture]
    public class ConcurrentQueueTests
    {
        [Test]
        public void Foo()
        {
            var checker = new Checker();
            checker.Enqueue(@"c:\Whatever.txt");
            checker.Enqueue(@"c:\Whatever2.txt");
            checker.Save(Filename("Index.json"));

            var checker2 = Checker.Load(Filename("Index.json"));
        }

        private string Filename(string path)
        {
            return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, path);
        }
    }
Only way I can make it work is to replace the queue with a List. Changing to a regular Queue presents ArgumentNull exception.

The only workaround I could think of so far is something ugly like the below which appeared to work but just adds empty/default objects on deserialize.
[JsonProperty, EditorBrowsable(EditorBrowsableState.Never), UsedImplicitly]
        protected List<CheckerItem> InternalList
        {
            get
            {
                return _queue.Any() ? _queue.ToList() : null;
            }
            set
            {
                if (value == null) return;
                foreach (var item in value)
                {
                    _queue.Enqueue(item);
                }
            }
        }
Thoughts? Thanks
Jan 29, 2015 at 4:33 PM
Hi,

I just came with the same problem with ConcurrentBag - I wrote a custom converter as follows:
    public class BagConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            if (!objectType.IsGenericType)
            {
                return false;
            }

            return objectType.GetGenericTypeDefinition() == typeof (ConcurrentBag<>);
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            serializer.Serialize(writer, value);
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            var objType = objectType.GetGenericArguments()[0];
            var listType = typeof(List<>).MakeGenericType(objType);
            var list = serializer.Deserialize(reader, listType);
            var bagType = typeof(ConcurrentBag<>).MakeGenericType(objType);
            var instance = Activator.CreateInstance(bagType, list);
            return instance;
        }

    }