Problem with serializing class with multiple generic indexers

Jan 6, 2010 at 1:22 PM

I know this is somewhat an unusual example but none the less I ran into it using the json serializer and thought I would report it with a possible fix.  What I have is a class that has multiple generic indexers defined, see the class "ThisGenericTest" for details:

public interface IKeyValueId
{
    int Id { get; set; }
    string Key { get; set; }
    string Value { get; set; }
}


public class KeyValueId : IKeyValueId
{
    public int Id { get; set; }
    public string Key { get; set; }
    public string Value { get; set; }
}

public class ThisGenericTest<T> where T : IKeyValueId
{
    private Dictionary<string, T> _dict1 = new Dictionary<string, T>();        
    
    public string MyProperty { get; set; }

    public void Add(T item)
    {
        this._dict1.Add(item.Key, item);
    }

    [JsonIgnore]
    public T this[string key]
    {
        get { return this._dict1[key]; }
        set { this._dict1[key] = value; }
    }

    [JsonIgnore]
    public T this[int id]
    {
        get { return this._dict1.Values.FirstOrDefault(x => x.Id == id); }
        set
        {
            var item = this[id];

            if (item == null)
                this.Add(value);
            else
                this._dict1[item.Key] = value;
        }
    }

    public string ToJson()
    {
        return JsonConvert.SerializeObject(this, Formatting.Indented);
    }

    public T[] TheItems
    {
        get { return this._dict1.Values.ToArray<T>(); }
        set
        {
            foreach (var item in value)
                this.Add(item);
        }
    }
}

And here is example usage:

ThisGenericTest<KeyValueId> g = new ThisGenericTest<KeyValueId>();

g.Add(new KeyValueId() { Id = 1, Key = "key1", Value = "value1" });
g.Add(new KeyValueId() { Id = 2, Key = "key2", Value = "value2" });

g.MyProperty = "some value";

var json = g.ToJson();

Console.WriteLine(json);

var gen = JsonConvert.DeserializeObject<ThisGenericTest<KeyValueId>>(json);

The problem is when the Utilities.ReflectionUtils.GetFieldsAndProperties method, when it loops through the groupedMembers to work around a problem with overriding a generic member on a base class, it ends up getting the following error:

 

System.InvalidOperationException was unhandled

  Message="Sequence contains no elements"

  Source="System.Core"

  StackTrace:

       at System.Linq.Enumerable.First[TSource](IEnumerable`1 source)

       at Newtonsoft.Json.Utilities.ReflectionUtils.GetFieldsAndProperties(Type type, BindingFlags bindingAttr) in E:\Work\Utils\Lib\DotNet\JsonNet\3.5\Src\Newtonsoft.Json\Utilities\ReflectionUtils.cs:line 569

As you can see the error is actually raised from the Linq library in System.Core, however, the problem stems from the assumption that it will find at least one member to add, which in this case is not true.  If you comment out one of the indexers in my ThisGenericTest class, you will see that it works fine, as long as you have the [JsonIgnore] attribute on the indexer.  If you don't it will get this error (snipped again):
Newtonsoft.Json.JsonSerializationException was unhandled
  Message="Error getting value from 'Item' on 'LookupStuff.ThisGenericTest`1[LookupStuff.KeyValueId]'."
  Source="Newtonsoft.Json"
  StackTrace:
       at Newtonsoft.Json.Serialization.DynamicValueProvider.GetValue(Object target) in E:\Work\Utils\Lib\DotNet\JsonNet\3.5\Src\Newtonsoft.Json\Serialization\DynamicValueProvider.cs:line 108
       at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeObject(JsonWriter writer, Object value, JsonObjectContract contract) in E:\Work\Utils\Lib\DotNet\JsonNet\3.5\Src\Newtonsoft.Json\Serialization\JsonSerializerInternalWriter.cs:line 307
I am not sure if the serializer should try to serialize indexer properties in the first place, when I serialize the same thing using the .Net xml serializer, it ignores the indexers.  Not a big deal since I can just ignore them but thought I would report it as well.  
Here is how I worked around it:
I replaced the line in the else clause:
distinctMembers.Add(groupedMember.Members.Where(m => !IsOverridenGenericMember(m, bindingAttr)).First());
with the following:
var gm = groupedMember.Members.Where(m => !IsOverridenGenericMember(m, bindingAttr)).FirstOrDefault();

if (gm == null)
  distinctMembers.Add(groupedMember.Members.First());
else
  distinctMembers.Add(gm);
I hope I have provided enough detail to help.  Great work on a very useful library.
Thank you,
Jim

 

 

System.InvalidOperationException was unhandled
  Message="Sequence contains no elements"
  Source="System.Core"
  StackTrace:
       at System.Linq.Enumerable.First[TSource](IEnumerable`1 source)
       at Newtonsoft.Json.Utilities.ReflectionUtils.GetFieldsAndProperties(Type type, BindingFlags bindingAttr) in E:\Work\Utils\Lib\DotNet\JsonNet\3.5\Src\Newtonsoft.Json\Utilities\ReflectionUtils.cs:line 569
Coordinator
Jan 8, 2010 at 7:46 AM

Hi

I've made it so that indexed properties are automatically ignored - it will fix your error and you won't need the ignore attributes. The latest version of the source code is here - http://json.codeplex.com/SourceControl/list/changesets