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:
parent
7bda253bab
commit
10304d811c
|
@ -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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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">
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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" />
|
||||||
|
|
Loading…
Reference in New Issue