Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for objects with IDictionary interface #54

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 70 additions & 1 deletion src/JSONWriter.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text;
Expand Down Expand Up @@ -133,10 +134,43 @@ static void AppendValue(StringBuilder stringBuilder, object item)
}
stringBuilder.Append('}');
}
else
else if (typeof(System.Dynamic.DynamicObject).IsAssignableFrom(type))
{
// support to convert DynamicObject to Json
stringBuilder.Append('{');
System.Dynamic.DynamicObject obj = item as System.Dynamic.DynamicObject;
bool isFirst = true;
foreach (var member in obj.GetDynamicMemberNames())
{
if (isFirst)
{
isFirst = false;
}
else
{
stringBuilder.Append(',');
}

var binder = Microsoft.CSharp.RuntimeBinder.Binder.GetMember(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags.None, member, item.GetType()
, new[] { Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfoFlags.None, null) });

var callsite = System.Runtime.CompilerServices.CallSite<Func<System.Runtime.CompilerServices.CallSite, object, object>>.Create(binder);
var value = callsite.Target(callsite, obj);

stringBuilder.Append('\"');
stringBuilder.Append(member);
stringBuilder.Append("\":");
AppendValue(stringBuilder, value);
}
stringBuilder.Append('}');
}
else
{
bool implementsIDictionary = type.GetInterfaces()
.Where(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IDictionary<,>))
.Any(x => x.GetGenericArguments()[0] == typeof(string));

stringBuilder.Append('{');
bool isFirst = true;
FieldInfo[] fieldInfos = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy);
for (int i = 0; i < fieldInfos.Length; i++)
Expand All @@ -151,6 +185,7 @@ static void AppendValue(StringBuilder stringBuilder, object item)
isFirst = false;
else
stringBuilder.Append(',');

stringBuilder.Append('\"');
stringBuilder.Append(GetMemberName(fieldInfos[i]));
stringBuilder.Append("\":");
Expand All @@ -163,20 +198,50 @@ static void AppendValue(StringBuilder stringBuilder, object item)
if (!propertyInfo[i].CanRead || propertyInfo[i].IsDefined(typeof(IgnoreDataMemberAttribute), true))
continue;

// ignore indexing parameter
if (propertyInfo[i].GetIndexParameters().Length > 0)
continue;

// ignore IDictionary properties if it is implemented unless explicitly stated otherwise
if (implementsIDictionary && IDictionaryProperties.Contains(propertyInfo[i].Name)
&& !propertyInfo[i].IsDefined(typeof(DataMemberAttribute), true))
{
continue;
}

object value = propertyInfo[i].GetValue(item, null);
if (value != null)
{
if (isFirst)
isFirst = false;
else
stringBuilder.Append(',');

stringBuilder.Append('\"');
stringBuilder.Append(GetMemberName(propertyInfo[i]));
stringBuilder.Append("\":");
AppendValue(stringBuilder, value);
}
}

// output IDictionary attributes if dictionary interface is implemented
if (implementsIDictionary)
{
dynamic dict = item;
foreach (string key in dict.Keys)
{
if (isFirst)
isFirst = false;
else
stringBuilder.Append(',');

stringBuilder.Append('\"');
stringBuilder.Append(key);
stringBuilder.Append("\":");
AppendValue(stringBuilder, dict[key]);
}
}

stringBuilder.Append('}');
}
}
Expand All @@ -192,5 +257,9 @@ static string GetMemberName(MemberInfo member)

return member.Name;
}

// index all the default properties from IDictionary interface
private static HashSet<string> IDictionaryProperties = typeof(IDictionary<,>).GetInterfaces().SelectMany(x => x.GetProperties())
.Concat(typeof(IDictionary<,>).GetProperties()).Select(x => x.Name).ToHashSet();
}
}
117 changes: 117 additions & 0 deletions test/TestWriter.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization;
using Microsoft.VisualStudio.TestTools.UnitTesting;
Expand Down Expand Up @@ -205,5 +206,121 @@ public void TestDatetime()
Assert.AreEqual(curTime.ToJson(), "\"" + DateTime.Now.ToString(System.Globalization.CultureInfo.InvariantCulture) + "\"");
}

public class DictionaryType : IDictionary<string, int>
{
public bool Base { get; } = true;

private Dictionary<string, int> dict_ = new Dictionary<string, int>();

public int this[string key] { get => ((IDictionary<string, int>)dict_)[key]; set => ((IDictionary<string, int>)dict_)[key] = value; }

public ICollection<string> Keys => ((IDictionary<string, int>)dict_).Keys;

public ICollection<int> Values => ((IDictionary<string, int>)dict_).Values;

[DataMemberAttribute]
public int Count => ((IDictionary<string, int>)dict_).Count;

public bool IsReadOnly => ((IDictionary<string, int>)dict_).IsReadOnly;

public void Add(string key, int value)
{
((IDictionary<string, int>)dict_).Add(key, value);
}

public void Add(KeyValuePair<string, int> item)
{
((IDictionary<string, int>)dict_).Add(item);
}

public void Clear()
{
((IDictionary<string, int>)dict_).Clear();
}

public bool Contains(KeyValuePair<string, int> item)
{
return ((IDictionary<string, int>)dict_).Contains(item);
}

public bool ContainsKey(string key)
{
return ((IDictionary<string, int>)dict_).ContainsKey(key);
}

public void CopyTo(KeyValuePair<string, int>[] array, int arrayIndex)
{
((IDictionary<string, int>)dict_).CopyTo(array, arrayIndex);
}

public IEnumerator<KeyValuePair<string, int>> GetEnumerator()
{
return ((IDictionary<string, int>)dict_).GetEnumerator();
}

public bool Remove(string key)
{
return ((IDictionary<string, int>)dict_).Remove(key);
}

public bool Remove(KeyValuePair<string, int> item)
{
return ((IDictionary<string, int>)dict_).Remove(item);
}

public bool TryGetValue(string key, out int value)
{
return ((IDictionary<string, int>)dict_).TryGetValue(key, out value);
}

IEnumerator IEnumerable.GetEnumerator()
{
return ((IDictionary<string, int>)dict_).GetEnumerator();
}
}

[TestMethod]
public void TestIDictionary()
{
Assert.AreEqual("{\"Base\":true,\"Count\":2,\"foo\":123,\"bar\":321}", new DictionaryType { { "foo", 123 }, { "bar", 321 } }.ToJson());
}

public class DictionaryTypeExtended : DictionaryType
{
public bool Ext { get; } = true;
}

[TestMethod]
public void TestIDictionaryExtended()
{
Assert.AreEqual("{\"Ext\":true,\"Base\":true,\"Count\":2,\"foo\":123,\"bar\":321}", new DictionaryTypeExtended { { "foo", 123 }, { "bar", 321 } }.ToJson());
}

class DynamicObjectType : System.Dynamic.DynamicObject
{
public override bool TryGetMember(System.Dynamic.GetMemberBinder binder, out object result)
{
result = null;

if (!dict_.ContainsKey(binder.Name))
return false;

result = dict_[binder.Name];
return true;
}

public override IEnumerable<string> GetDynamicMemberNames()
{
return dict_.Keys;
}

private Dictionary<string, object> dict_ = new Dictionary<string, object> { { "Name", "TestObject" }, { "Length", 10 } };
}

[TestMethod]
public void TestDynamicObject()
{
Assert.AreEqual("{\"Name\":\"TestObject\",\"Length\":10}", new DynamicObjectType { }.ToJson());
}
}
}