Handle dictionaries with non-object values.

In cases where a Dictionary property had a non-object value type, the
dictionary couldn't be treated like an object. This code will wrap a
dictionary so its non-object values are upcast to objects.
This commit is contained in:
Travis Parks 2014-05-20 19:34:54 -04:00
parent 7bda253bab
commit 10304d811c
5 changed files with 376 additions and 1 deletions

View File

@ -0,0 +1,206 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Mustache.Test
{
[TestClass]
public class UpcastDictionaryTester
{
[TestMethod]
public void ShouldReturnNullForNull()
{
IDictionary<string, object> result = UpcastDictionary.Create(null);
Assert.IsNull(result, "Null should be returned for null.");
}
[TestMethod]
public void ShouldReturnArgumentIfIDictionary_string_object()
{
object source = new Dictionary<string, object>();
IDictionary<string, object> result = UpcastDictionary.Create(source);
Assert.AreSame(source, result, "The up-cast wrapper should not be applied if already a IDictionary<string, object>.");
}
[TestMethod]
public void ShouldReturnNullIfNotGenericType()
{
object source = String.Empty;
IDictionary<string, object> result = UpcastDictionary.Create(source);
Assert.IsNull(result, "Null should be returned for non-generic types.");
}
[TestMethod]
public void ShouldReturnNullIfWrongNumberOfGenericArguments()
{
object source = new List<string>();
IDictionary<string, object> result = UpcastDictionary.Create(source);
Assert.IsNull(result, "Null should be returned for generic types with the wrong number of type arguments.");
}
[TestMethod]
public void ShouldReturnNullIfFirstGenericTypeArgumentIsNotAString()
{
object source = new Dictionary<object, object>();
IDictionary<string, object> result = UpcastDictionary.Create(source);
Assert.IsNull(result, "Null should be returned if the first generic type argument is not a string.");
}
[TestMethod]
public void ShouldReturnNullIfNotDictionaryType()
{
object source = (Converter<string, object>)(s => (object)s);
IDictionary<string, object> result = UpcastDictionary.Create(source);
Assert.IsNull(result, "Null should be returned for non-dictionary types.");
}
[TestMethod]
public void ShouldReturnUpcastWrapperForDictionary_string_TValue()
{
object source = new Dictionary<string, string>();
IDictionary<string, object> result = UpcastDictionary.Create(source);
Assert.IsInstanceOfType(result, typeof(UpcastDictionary<string>), "The source was not wrapped.");
}
[TestMethod]
public void ShouldFindKeyIfInWrappedDictionary()
{
object source = new Dictionary<string, string>() { { "Name", "Bob" } };
IDictionary<string, object> result = UpcastDictionary.Create(source);
bool containsKey = result.ContainsKey("Name");
Assert.IsTrue(containsKey, "The key Name should have been found.");
}
[TestMethod]
public void ShouldNotFindKeyIfNotInWrappedDictionary()
{
object source = new Dictionary<string, string>() { { "Name", "Bob" } };
IDictionary<string, object> result = UpcastDictionary.Create(source);
bool containsKey = result.ContainsKey("Age");
Assert.IsFalse(containsKey, "The key Age should not have been found.");
}
[TestMethod]
public void ShouldFindKeysInWrappedDictionary()
{
var source = new Dictionary<string, string>() { { "Name", "Bob" }, { "Age", "100" } };
IDictionary<string, object> result = UpcastDictionary.Create(source);
ICollection sourceKeys = source.Keys;
ICollection wrappedKeys = result.Keys.ToArray();
CollectionAssert.AreEquivalent(sourceKeys, wrappedKeys, "The same keys should have been found in both collections.");
}
[TestMethod]
public void ShouldFindKeyIfInWrappedDictionary_TryGetValue()
{
var source = new Dictionary<string, string>() { { "Name", "Bob" } };
IDictionary<string, object> result = UpcastDictionary.Create(source);
object value;
bool found = result.TryGetValue("Name", out value);
Assert.IsTrue(found, "The key should have been found.");
Assert.AreSame(source["Name"], value, "The value in the underlying dictionary should have been returned.");
}
[TestMethod]
public void ShouldNotFindKeyIfNotInWrappedDictionary_TryGetValue()
{
var source = new Dictionary<string, int>() { { "Age", 100 } };
IDictionary<string, object> result = UpcastDictionary.Create(source);
object value;
bool found = result.TryGetValue("Name", out value);
Assert.IsFalse(found, "The key should not have been found.");
Assert.IsNull(value, "The value should be null even if the actual type is a struct.");
}
[TestMethod]
public void ShouldReturnValuesAsObjects()
{
var source = new Dictionary<string, int>() { { "Age", 100 }, { "Weight", 500 } };
IDictionary<string, object> result = UpcastDictionary.Create(source);
ICollection sourceValues = source.Values;
ICollection wrappedValues = result.Values.ToArray();
CollectionAssert.AreEquivalent(sourceValues, wrappedValues, "The underlying values were not returned.");
}
[TestMethod]
public void ShouldFindKeyIfInWrappedDictionary_Indexer()
{
var source = new Dictionary<string, string>() { { "Name", "Bob" } };
IDictionary<string, object> result = UpcastDictionary.Create(source);
object value = result["Name"];
Assert.AreSame(source["Name"], value, "The value in the underlying dictionary should have been returned.");
}
[TestMethod]
[ExpectedException(typeof(KeyNotFoundException))]
public void ShouldNotFindKeyIfNotInWrappedDictionary_Indexer()
{
var source = new Dictionary<string, int>() { { "Age", 100 } };
IDictionary<string, object> result = UpcastDictionary.Create(source);
object value = result["Name"];
}
[TestMethod]
public void ShouldNotFindPairIfValueWrongType()
{
var source = new Dictionary<string, int>() { { "Age", 100 } };
IDictionary<string, object> result = UpcastDictionary.Create(source);
bool contains = result.Contains(new KeyValuePair<string, object>("Age", "Blah"));
Assert.IsFalse(contains, "The pair should not have been found.");
}
[TestMethod]
public void ShouldFindPairInWrappedDictionary()
{
var source = new Dictionary<string, int>() { { "Age", 100 } };
IDictionary<string, object> result = UpcastDictionary.Create(source);
bool contains = result.Contains(new KeyValuePair<string, object>("Age", 100));
Assert.IsTrue(contains, "The pair should have been found.");
}
[TestMethod]
public void ShouldCopyPairsToArray()
{
var source = new Dictionary<string, int>() { { "Age", 100 }, { "Weight", 45 } };
IDictionary<string, object> result = UpcastDictionary.Create(source);
var array = new KeyValuePair<string, object>[2];
result.CopyTo(array, 0);
var expected = new KeyValuePair<string, object>[]
{
new KeyValuePair<string, object>("Age", 100),
new KeyValuePair<string, object>("Weight", 45)
};
CollectionAssert.AreEqual(expected, array, "The pairs were not copied.");
}
[TestMethod]
public void ShouldGetCount()
{
var source = new Dictionary<string, int>() { { "Age", 100 }, { "Weight", 45 } };
IDictionary<string, object> result = UpcastDictionary.Create(source);
Assert.AreEqual(source.Count, result.Count, "The source and Upcast dictionary should have the same count.");
}
[TestMethod]
public void ShouldGetEnumerator()
{
var source = new Dictionary<string, int>() { { "Age", 100 }, { "Weight", 45 } };
IDictionary<string, object> result = UpcastDictionary.Create(source);
IEnumerator<KeyValuePair<string, object>> enumerator = result.GetEnumerator();
var values = new List<KeyValuePair<string, object>>();
while (enumerator.MoveNext())
{
values.Add(enumerator.Current);
}
var expected = new KeyValuePair<string, object>[]
{
new KeyValuePair<string, object>("Age", 100),
new KeyValuePair<string, object>("Weight", 45)
};
CollectionAssert.AreEqual(expected, values, "The enumerator did not return the correct pairs.");
}
}
}

View File

@ -45,6 +45,7 @@
<ItemGroup> <ItemGroup>
<Compile Include="FormatCompilerTester.cs" /> <Compile Include="FormatCompilerTester.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="UpcastDictionaryTester.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\mustache-sharp\mustache-sharp.csproj"> <ProjectReference Include="..\mustache-sharp\mustache-sharp.csproj">

View File

@ -125,7 +125,7 @@ namespace Mustache
private static IDictionary<string, object> toLookup(object value) private static IDictionary<string, object> toLookup(object value)
{ {
IDictionary<string, object> lookup = value as IDictionary<string, object>; IDictionary<string, object> lookup = UpcastDictionary.Create(value);
if (lookup == null) if (lookup == null)
{ {
lookup = new PropertyDictionary(value); lookup = new PropertyDictionary(value);

View File

@ -0,0 +1,167 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
namespace Mustache
{
internal static class UpcastDictionary
{
public static IDictionary<string, object> Create(object source)
{
if (source == null)
{
return null;
}
IDictionary<string, object> sourceDictionary = source as IDictionary<string, object>;
if (sourceDictionary != null)
{
return sourceDictionary;
}
Type sourceType = source.GetType();
if (!sourceType.IsGenericType)
{
return null;
}
Type[] argumentTypes = sourceType.GetGenericArguments();
if (argumentTypes.Length != 2)
{
return null;
}
Type keyType = argumentTypes[0];
if (keyType != typeof(string))
{
return null;
}
Type valueType = argumentTypes[1];
Type genericType = typeof(IDictionary<,>).MakeGenericType(typeof(string), valueType);
if (!genericType.IsAssignableFrom(sourceType))
{
return null;
}
Type upcastType = typeof(UpcastDictionary<>).MakeGenericType(valueType);
return (IDictionary<string, object>)Activator.CreateInstance(upcastType, source);
}
}
internal class UpcastDictionary<TValue> : IDictionary<string, object>
{
private readonly IDictionary<string, TValue> dictionary;
public UpcastDictionary(IDictionary<string, TValue> dictionary)
{
this.dictionary = dictionary;
}
[EditorBrowsable(EditorBrowsableState.Never)]
void IDictionary<string, object>.Add(string key, object value)
{
throw new NotSupportedException();
}
public bool ContainsKey(string key)
{
return dictionary.ContainsKey(key);
}
public ICollection<string> Keys
{
get { return dictionary.Keys; }
}
[EditorBrowsable(EditorBrowsableState.Never)]
bool IDictionary<string, object>.Remove(string key)
{
throw new NotSupportedException();
}
public bool TryGetValue(string key, out object value)
{
TValue result;
if (dictionary.TryGetValue(key, out result))
{
value = result;
return true;
}
else
{
value = null;
return false;
}
}
public ICollection<object> Values
{
get { return dictionary.Values.Cast<object>().ToArray(); }
}
public object this[string key]
{
get
{
return dictionary[key];
}
[EditorBrowsable(EditorBrowsableState.Never)]
set
{
throw new NotSupportedException();
}
}
[EditorBrowsable(EditorBrowsableState.Never)]
void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item)
{
throw new NotSupportedException();
}
[EditorBrowsable(EditorBrowsableState.Never)]
void ICollection<KeyValuePair<string, object>>.Clear()
{
throw new NotSupportedException();
}
bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item)
{
if (!(item.Value is TValue))
{
return false;
}
KeyValuePair<string, TValue> pair = new KeyValuePair<string,TValue>(item.Key, (TValue)item.Value);
ICollection<KeyValuePair<string, TValue>> collection = dictionary;
return dictionary.Contains(pair);
}
void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
{
var pairs = dictionary.Select(p => new KeyValuePair<string, object>(p.Key, p.Value)).ToArray();
pairs.CopyTo(array, arrayIndex);
}
public int Count
{
get { return dictionary.Count; }
}
bool ICollection<KeyValuePair<string, object>>.IsReadOnly
{
get { return true; }
}
[EditorBrowsable(EditorBrowsableState.Never)]
bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item)
{
throw new NotSupportedException();
}
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
return dictionary.Select(p => new KeyValuePair<string, object>(p.Key, p.Value)).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}

View File

@ -40,6 +40,7 @@
<Compile Include="ContentTagDefinition.cs" /> <Compile Include="ContentTagDefinition.cs" />
<Compile Include="Context.cs" /> <Compile Include="Context.cs" />
<Compile Include="ContextParameter.cs" /> <Compile Include="ContextParameter.cs" />
<Compile Include="UpcastDictionary.cs" />
<Compile Include="VariableFoundEventArgs.cs" /> <Compile Include="VariableFoundEventArgs.cs" />
<Compile Include="SetTagDefinition.cs" /> <Compile Include="SetTagDefinition.cs" />
<Compile Include="NewlineTagDefinition.cs" /> <Compile Include="NewlineTagDefinition.cs" />