From 8220e046cc81d7e0eac335426bf2dc77461dd5de Mon Sep 17 00:00:00 2001 From: Shanaka Dharmawardhana Date: Sun, 30 Apr 2023 03:12:15 +0530 Subject: [PATCH 1/2] Support for objects with IDictionary interface Fix crash when indexing properties available in object types Signed-off-by: Shanaka Dharmawardhana --- src/JSONWriter.cs | 41 ++++++++++++++++++++- test/TestWriter.cs | 90 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+), 1 deletion(-) diff --git a/src/JSONWriter.cs b/src/JSONWriter.cs index 9ae38ed..e9487ca 100644 --- a/src/JSONWriter.cs +++ b/src/JSONWriter.cs @@ -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; @@ -135,8 +136,11 @@ static void AppendValue(StringBuilder stringBuilder, object item) } else { - stringBuilder.Append('{'); + 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++) @@ -151,6 +155,7 @@ static void AppendValue(StringBuilder stringBuilder, object item) isFirst = false; else stringBuilder.Append(','); + stringBuilder.Append('\"'); stringBuilder.Append(GetMemberName(fieldInfos[i])); stringBuilder.Append("\":"); @@ -163,6 +168,17 @@ 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) { @@ -170,6 +186,7 @@ static void AppendValue(StringBuilder stringBuilder, object item) isFirst = false; else stringBuilder.Append(','); + stringBuilder.Append('\"'); stringBuilder.Append(GetMemberName(propertyInfo[i])); stringBuilder.Append("\":"); @@ -177,6 +194,24 @@ static void AppendValue(StringBuilder stringBuilder, object item) } } + // 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('}'); } } @@ -192,5 +227,9 @@ static string GetMemberName(MemberInfo member) return member.Name; } + + // index all the default properties from IDictionary interface + private static HashSet IDictionaryProperties = typeof(IDictionary<,>).GetInterfaces().SelectMany(x => x.GetProperties()) + .Concat(typeof(IDictionary<,>).GetProperties()).Select(x => x.Name).ToHashSet(); } } diff --git a/test/TestWriter.cs b/test/TestWriter.cs index 7dc645c..a6224f3 100644 --- a/test/TestWriter.cs +++ b/test/TestWriter.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Runtime.Serialization; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -205,5 +206,94 @@ public void TestDatetime() Assert.AreEqual(curTime.ToJson(), "\"" + DateTime.Now.ToString(System.Globalization.CultureInfo.InvariantCulture) + "\""); } + public class DictionaryType : IDictionary + { + public bool Base { get; } = true; + + private Dictionary dict_ = new Dictionary(); + + public int this[string key] { get => ((IDictionary)dict_)[key]; set => ((IDictionary)dict_)[key] = value; } + + public ICollection Keys => ((IDictionary)dict_).Keys; + + public ICollection Values => ((IDictionary)dict_).Values; + + [DataMemberAttribute] + public int Count => ((IDictionary)dict_).Count; + + public bool IsReadOnly => ((IDictionary)dict_).IsReadOnly; + + public void Add(string key, int value) + { + ((IDictionary)dict_).Add(key, value); + } + + public void Add(KeyValuePair item) + { + ((IDictionary)dict_).Add(item); + } + + public void Clear() + { + ((IDictionary)dict_).Clear(); + } + + public bool Contains(KeyValuePair item) + { + return ((IDictionary)dict_).Contains(item); + } + + public bool ContainsKey(string key) + { + return ((IDictionary)dict_).ContainsKey(key); + } + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + ((IDictionary)dict_).CopyTo(array, arrayIndex); + } + + public IEnumerator> GetEnumerator() + { + return ((IDictionary)dict_).GetEnumerator(); + } + + public bool Remove(string key) + { + return ((IDictionary)dict_).Remove(key); + } + + public bool Remove(KeyValuePair item) + { + return ((IDictionary)dict_).Remove(item); + } + + public bool TryGetValue(string key, out int value) + { + return ((IDictionary)dict_).TryGetValue(key, out value); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IDictionary)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()); + } } } From 01e8a7dd1bee7bcb1bdf0db91166e1de8038162a Mon Sep 17 00:00:00 2001 From: shanakard Date: Sun, 30 Apr 2023 04:02:31 +0530 Subject: [PATCH 2/2] Support for DynamicObjects Signed-off-by: shanakard --- src/JSONWriter.cs | 30 ++++++++++++++++++++++++++++++ test/TestWriter.cs | 27 +++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/src/JSONWriter.cs b/src/JSONWriter.cs index e9487ca..5e28f8c 100644 --- a/src/JSONWriter.cs +++ b/src/JSONWriter.cs @@ -134,6 +134,36 @@ static void AppendValue(StringBuilder stringBuilder, object item) } stringBuilder.Append('}'); } + 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>.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() diff --git a/test/TestWriter.cs b/test/TestWriter.cs index a6224f3..19d28c0 100644 --- a/test/TestWriter.cs +++ b/test/TestWriter.cs @@ -295,5 +295,32 @@ 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 GetDynamicMemberNames() + { + return dict_.Keys; + } + + private Dictionary dict_ = new Dictionary { { "Name", "TestObject" }, { "Length", 10 } }; + } + + [TestMethod] + public void TestDynamicObject() + { + Assert.AreEqual("{\"Name\":\"TestObject\",\"Length\":10}", new DynamicObjectType { }.ToJson()); + } } }