Serializing Generic Dictionaries

Feb 1, 2010 at 7:43 PM

While serializing a generic list, I got the following error: Unable to cast object of type 'X' to type 'System.Collections.IDictionary'

I fixed the problem by making the following changes to JsonSerializerInternalWriter.cs:

I added a new method called SerializeGenericDictionary

private void SerializeGenericDictionary(JsonWriter writer, IWrappedDictionary values, JsonDictionaryContract contract)
    {
        contract.InvokeOnSerializing(values, Serializer.Context);

        SerializeStack.Add(values);
        writer.WriteStartObject();

        bool isReference = contract.IsReference ?? HasFlag(Serializer.PreserveReferencesHandling, PreserveReferencesHandling.Objects);
        if (isReference)
        {
            writer.WritePropertyName(JsonTypeReflector.IdPropertyName);
            writer.WriteValue(Serializer.ReferenceResolver.GetReference(values));
        }
        if (HasFlag(Serializer.TypeNameHandling, TypeNameHandling.Objects))
        {
            WriteTypeProperty(writer, values.GetType());
        }

        int initialDepth = writer.Top;

        foreach (object entry in values.Keys)
        {
            string propertyName = entry.ToString();

            try
            {
                object value = values[entry];
                JsonContract valueContract = GetContractSafe(value);

                if (ShouldWriteReference(value, null, valueContract))
                {
                    writer.WritePropertyName(propertyName);
                    WriteReference(writer, value);
                }
                else
                {
                    if (!CheckForCircularReference(value, null))
                        continue;

                    writer.WritePropertyName(propertyName);

                    SerializeValue(writer, value, null, valueContract);
                }
            }
            catch (Exception ex)
            {
                if (IsErrorHandled(values, contract, propertyName, ex))
                    HandleError(writer, initialDepth);
                else
                    throw;
            }
        }

        writer.WriteEndObject();
        SerializeStack.RemoveAt(SerializeStack.Count - 1);

        contract.InvokeOnSerialized(values, Serializer.Context);
    }

I also changed the function SerializeValue:

    private void SerializeValue(JsonWriter writer, object value, JsonConverter memberConverter, JsonContract contract)
    {
      JsonConverter converter = memberConverter;

      if (value == null)
      {
        writer.WriteNull();
        return;
      }

      if (converter != null
          || ((converter = contract.Converter) != null)
          || Serializer.HasMatchingConverter(contract.UnderlyingType, out converter))
      {
        SerializeConvertable(writer, converter, value, contract);
      }
      else if (contract is JsonPrimitiveContract)
      {
        writer.WriteValue(value);
      }
      else if (value is JToken)
      {
        ((JToken) value).WriteTo(writer, (Serializer.Converters != null) ? Serializer.Converters.ToArray() : null);
      }
      else if (contract is JsonObjectContract)
      {
        SerializeObject(writer, value, (JsonObjectContract) contract);
      }
      else if (contract is JsonDictionaryContract)
      {
          if (value.GetType().IsAssignableFrom(typeof(IDictionary)))
          {
              SerializeDictionary(writer, (IDictionary)value, (JsonDictionaryContract)contract);
          }
          else
          {
              SerializeGenericDictionary(writer, ((JsonDictionaryContract)contract).CreateWrapper(value), (JsonDictionaryContract)contract);
          }
      }
      else if (contract is JsonArrayContract)
      {
        if (value is IList)
        {
          SerializeList(writer, (IList) value, (JsonArrayContract) contract);
        }
        else if (value is IEnumerable)
        {
          SerializeEnumerable(writer, (IEnumerable) value, (JsonArrayContract) contract);
        }
        else
        {
          throw new Exception(
            "Cannot serialize '{0}' into a JSON array. Type does not implement IEnumerable.".FormatWith(
              CultureInfo.InvariantCulture, value.GetType()));
        }
      }
    }

 

Coordinator
Feb 2, 2010 at 9:40 AM

Can you post the code of the class you are serializing? I want to see the error myself before changing any code.

Feb 2, 2010 at 3:32 PM

James,

I am serializing the System.Web.Mvc.ModelStateDictionary object from the ASP.NET MVC.  Here's the code:

/* ****************************************************************************
 *
 * Copyright (c) Microsoft Corporation. All rights reserved.
 *
 * This software is subject to the Microsoft Public License (Ms-PL). 
 * A copy of the license can be found in the license.htm file included 
 * in this distribution.
 *
 * You must not remove this notice, or any other, from this software.
 *
 * ***************************************************************************/

namespace System.Web.Mvc {
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Diagnostics.CodeAnalysis;
    using System.Linq;

    [Serializable]
    public class ModelStateDictionary : IDictionary<string, ModelState> {

        private readonly Dictionary<string, ModelState> _innerDictionary = new Dictionary<string, ModelState>(StringComparer.OrdinalIgnoreCase);

        public ModelStateDictionary() {
        }

        public ModelStateDictionary(ModelStateDictionary dictionary) {
            if (dictionary == null) {
                throw new ArgumentNullException("dictionary");
            }

            foreach (var entry in dictionary) {
                _innerDictionary.Add(entry.Key, entry.Value);
            }
        }

        [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
        public int Count {
            get {
                return _innerDictionary.Count;
            }
        }

        [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
        public bool IsReadOnly {
            get {
                return ((IDictionary<string, ModelState>)_innerDictionary).IsReadOnly;
            }
        }

        public bool IsValid {
            get {
                return Values.All(modelState => modelState.Errors.Count == 0);
            }
        }

        [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
        public ICollection<string> Keys {
            get {
                return _innerDictionary.Keys;
            }
        }

        [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
        public ModelState this[string key] {
            get {
                ModelState value;
                _innerDictionary.TryGetValue(key, out value);
                return value;
            }
            set {
                _innerDictionary[key] = value;
            }
        }

        [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
        public ICollection<ModelState> Values {
            get {
                return _innerDictionary.Values;
            }
        }

        [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
        public void Add(KeyValuePair<string, ModelState> item) {
            ((IDictionary<string, ModelState>)_innerDictionary).Add(item);
        }

        [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
        public void Add(string key, ModelState value) {
            _innerDictionary.Add(key, value);
        }

        public void AddModelError(string key, Exception exception) {
            GetModelStateForKey(key).Errors.Add(exception);
        }

        public void AddModelError(string key, string errorMessage) {
            GetModelStateForKey(key).Errors.Add(errorMessage);
        }

        [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
        public void Clear() {
            _innerDictionary.Clear();
        }

        [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
        public bool Contains(KeyValuePair<string, ModelState> item) {
            return ((IDictionary<string, ModelState>)_innerDictionary).Contains(item);
        }

        [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
        public bool ContainsKey(string key) {
            return _innerDictionary.ContainsKey(key);
        }

        [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
        public void CopyTo(KeyValuePair<string, ModelState>[] array, int arrayIndex) {
            ((IDictionary<string, ModelState>)_innerDictionary).CopyTo(array, arrayIndex);
        }

        [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
        public IEnumerator<KeyValuePair<string, ModelState>> GetEnumerator() {
            return _innerDictionary.GetEnumerator();
        }

        private ModelState GetModelStateForKey(string key) {
            if (key == null) {
                throw new ArgumentNullException("key");
            }

            ModelState modelState;
            if (!TryGetValue(key, out modelState)) {
                modelState = new ModelState();
                this[key] = modelState;
            }

            return modelState;
        }

        public bool IsValidField(string key) {
            if (key == null) {
                throw new ArgumentNullException("key");
            }

            // if the key is not found in the dictionary, we just say that it's valid (since there are no errors)
            return DictionaryHelpers.FindKeysWithPrefix(this, key).All(entry => entry.Value.Errors.Count == 0);
        }

        public void Merge(ModelStateDictionary dictionary) {
            if (dictionary == null) {
                return;
            }

            foreach (var entry in dictionary) {
                this[entry.Key] = entry.Value;
            }
        }

        [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
        public bool Remove(KeyValuePair<string, ModelState> item) {
            return ((IDictionary<string, ModelState>)_innerDictionary).Remove(item);
        }

        [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
        public bool Remove(string key) {
            return _innerDictionary.Remove(key);
        }

        public void SetModelValue(string key, ValueProviderResult value) {
            GetModelStateForKey(key).Value = value;
        }

        [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
        public bool TryGetValue(string key, out ModelState value) {
            return _innerDictionary.TryGetValue(key, out value);
        }

        #region IEnumerable Members
        [SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
        IEnumerator IEnumerable.GetEnumerator() {
            return ((IEnumerable)_innerDictionary).GetEnumerator();
        }
        #endregion

    }
}

 

Coordinator
Feb 4, 2010 at 9:23 AM

I've fixed your bug, although in a different way, and have added the source code change to CodePlex. Thanks for letting me know about it.

http://json.codeplex.com/SourceControl/list/changesets

 

 

Feb 4, 2010 at 4:07 PM

Thanks for taking care of it so quickly.