diff --git a/UnitTests/Tests.cs b/UnitTests/Tests.cs index 006477b..114c63e 100644 --- a/UnitTests/Tests.cs +++ b/UnitTests/Tests.cs @@ -1166,6 +1166,39 @@ public static void CustomTypes() Assert.AreEqual(ip.ip, o.ip); } + public class CollectionDto + { + public IDictionary Dictionary = new Dictionary(); + public IDictionary Dictionary2 = new Dictionary(); + public ISet Set; + public ICollection Collection; + public IList OrderedCollection; + } + + [Test] + public static void CollectionInterfaces() + { + var dto = new CollectionDto + { + Dictionary = {{"test", 2}, {"test2", 3}}, + Dictionary2 = {{1, 2}, {2, 4}}, + Set = new SortedSet {"5", "3", "7"}, + Collection = new[] {"2", "8", "5"}, + OrderedCollection = new[] {DateTime.Now, DateTime.MaxValue} + }; + + var s = BJSON.ToBJSON(dto); + var o = BJSON.ToObject(s); + + Assert.IsNotNull(o); + CollectionAssert.AreEquivalent(dto.Dictionary, o.Dictionary); + CollectionAssert.AreEquivalent(dto.Dictionary2, o.Dictionary2); + CollectionAssert.AreEquivalent(dto.Set, o.Set); + Assert.IsTrue(o.Set.SetEquals(dto.Set)); + CollectionAssert.AreEqual(dto.Collection, o.Collection); + CollectionAssert.AreEqual(dto.OrderedCollection, o.OrderedCollection); + } + public class readonlyProps { public List Collection { get; } diff --git a/fastBinaryJSON/BJSON.cs b/fastBinaryJSON/BJSON.cs index f8a5216..ddeafaa 100644 --- a/fastBinaryJSON/BJSON.cs +++ b/fastBinaryJSON/BJSON.cs @@ -6,6 +6,7 @@ #endif using System.IO; using System.Collections.Specialized; +using System.Linq.Expressions; using fastJSON; namespace fastBinaryJSON @@ -215,7 +216,7 @@ public static byte[] ToBJSON(object obj, BJSONParameters param) return new BJSONSerializer(param).ConvertToBJSON(obj); } /// - /// Fill a given object with the binary json represenation + /// Fill a given object with the binary json representation /// /// /// @@ -605,8 +606,8 @@ private object ParseDictionary(Dictionary d, Dictionary)v, pi.pt, pi.bt, globaltypes); + if (pi.IsGenericType && !pi.IsValueType) + oset = CreateGenericCollection((List)v, pi.pt, pi.bt, globaltypes); else if ((pi.IsClass || pi.IsStruct || pi.IsInterface) && v is Dictionary) { var oo = (Dictionary)v; @@ -710,11 +711,11 @@ private object CreateArray(List data, Type pt, Type bt, Dictionary data, Type pt, Type bt, Dictionary globalTypes) + private object CreateGenericList(IEnumerable data, Type pt, Type bt, Dictionary globalTypes) { if (pt != typeof(object)) { - IList col = (IList)Reflection.Instance.FastCreateList(pt, data.Count); + var col = (IList)Reflection.Instance.FastCreateList(pt.IsInterface ? Reflection.Instance.GetGenericType(typeof(List<>),bt ?? typeof(object)) : pt, data.Count); // create an array of objects foreach (object ob in data) { @@ -738,14 +739,64 @@ private object CreateGenericList(List data, Type pt, Type bt, Dictionary return data; } + private object CreateGenericCollection(IEnumerable data, Type pt, Type bt, Dictionary globalTypes) + { + if (pt != typeof(object)) + { + var objType = pt; + if (pt.IsInterface) + { +#if NET4 + // ISet is also a collection interface, but is not implemented by any List or Dictionary, so use HashSet as default implementation + objType = new[] {typeof(List<>), typeof(HashSet<>)} + .Select(t => Reflection.Instance.GetGenericType(t, bt ?? typeof(object))) + .FirstOrDefault(pt.IsAssignableFrom); +#else + objType = Reflection.Instance.GetGenericType(typeof(List<>), bt ?? typeof(object)); + if (!pt.IsAssignableFrom(objType)) + objType = null; +#endif + if (objType == null) + throw new NotSupportedException("Unable to resolve an implementation type for " + pt.FullName); + } + if (typeof(IList).IsAssignableFrom(objType)) + return CreateGenericList(data, objType, bt, globalTypes); + // create an array of objects + var add = Reflection.Instance.GetCollectionAdder(objType); + if (add == null) throw new NotSupportedException("Unable to find an Add() method for collection " + objType.FullName); + var col = Reflection.Instance.FastCreateInstance(objType); + + foreach (object ob in data) + { + if (ob is IDictionary) + add(col, ParseDictionary((Dictionary) ob, globalTypes, bt, null)); + + else if (ob is List) + { + if (bt.IsGenericType) + add(col, (List) ob); + else + add(col, ((List) ob).ToArray()); + } + else if (ob is typedarray) + add(col, ((typedarray) ob).data.ToArray()); + else + add(col, ob); + } + return col; + } + return data; + } + private object CreateStringKeyDictionary(Dictionary reader, Type pt, Type[] types, Dictionary globalTypes) { - var col = (IDictionary)Reflection.Instance.FastCreateInstance(pt); Type arraytype = null; Type t2 = null; if (types != null) t2 = types[1]; + var col = (IDictionary)Reflection.Instance.FastCreateInstance(pt.IsInterface ? Reflection.Instance.GetGenericType(typeof(Dictionary<,>), typeof(string), t2??typeof(object)) : pt); + Type generictype = null; var ga = Reflection.Instance.GetGenericArguments(t2); if (ga.Length > 0) @@ -781,7 +832,6 @@ private object CreateStringKeyDictionary(Dictionary reader, Type private object CreateDictionary(List reader, Type pt, Type[] types, Dictionary globalTypes) { - IDictionary col = (IDictionary)Reflection.Instance.FastCreateInstance(pt); Type t1 = null; Type t2 = null; if (types != null) @@ -790,6 +840,8 @@ private object CreateDictionary(List reader, Type pt, Type[] types, Dict t2 = types[1]; } + var col = (IDictionary)Reflection.Instance.FastCreateInstance(pt.IsInterface && t1!=null ? Reflection.Instance.GetGenericType(typeof(Dictionary<,>), t1, t2??typeof(object)) : pt); + foreach (Dictionary values in reader) { object key = values["k"]; diff --git a/fastBinaryJSON/Reflection.cs b/fastBinaryJSON/Reflection.cs index 88aa2fd..16a1486 100644 --- a/fastBinaryJSON/Reflection.cs +++ b/fastBinaryJSON/Reflection.cs @@ -12,6 +12,7 @@ using System.Data; #endif using System.Collections.Specialized; +using System.Linq.Expressions; namespace fastJSON { @@ -94,6 +95,7 @@ private Reflection() public delegate object GenericGetter(object obj); private delegate object CreateObject(); private delegate object CreateList(int capacity); + internal delegate void AddItemToCollection(object collection, object item); private SafeDictionary _tyname = new SafeDictionary(10); private SafeDictionary _typecache = new SafeDictionary(10); @@ -101,9 +103,11 @@ private Reflection() private SafeDictionary _conlistcache = new SafeDictionary(10); private SafeDictionary _getterscache = new SafeDictionary(10); private SafeDictionary> _propertycache = new SafeDictionary>(10); - private SafeDictionary _genericTypes = new SafeDictionary(10); + private SafeDictionary _genericArguments = new SafeDictionary(10); private SafeDictionary _genericTypeDef = new SafeDictionary(10); - private static SafeDictionary _opCodes; + private SafeDictionary _genericTypes = new SafeDictionary(10); + private SafeDictionary _genericCollectionAdders = new SafeDictionary(10); + private static Dictionary _opCodes; private static List _blacklistTypes = new List() { "system.configuration.install.assemblyinstaller", @@ -114,6 +118,66 @@ private Reflection() "microsoft.exchange.management.systemmanager.winforms.exchangesettingsprovider" }; + #region private implementation types + /// + /// Composite key to lookup cached constructed generic types. + /// + private struct GenericTypeKey : IEquatable + { + private readonly Type _genericDefinition; + private readonly Type[] _parameters; + + public Type[] GetParameters() + { + // Results of this method are not exposed, so we can omit making a copy to keep the instance immutable + return _parameters ?? Type.EmptyTypes; + } + + public GenericTypeKey(Type genericDefinition, params Type[] parameters) + { + _genericDefinition = genericDefinition; + // input comes from another class, just make a copy to be sure the array's elements do not change after creation. + _parameters = new Type[parameters.Length]; + Array.Copy(parameters, _parameters, parameters.Length); + } + + public bool Equals(GenericTypeKey other) + { + if (_genericDefinition != other._genericDefinition || (_parameters?.Length ?? -1) != (other._parameters?.Length ?? -1)) + return false; + if (_parameters == null && other._parameters == null) return true; // if one is null the other is too at this point, so checking only one would be enough + for (int i = 0; i < _parameters.Length; i++) + { + if (_parameters[i] != other._parameters[i]) + return false; + } + return true; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + return obj is GenericTypeKey && Equals((GenericTypeKey) obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = (_genericDefinition != null ? _genericDefinition.GetHashCode() : 0); + if (_parameters == null) return (hashCode * 397); + hashCode = (hashCode * 397) ^ _parameters.Length.GetHashCode(); + foreach (Type t in _parameters) + { + hashCode = (hashCode * 397) ^ (t != null ? t.GetHashCode() : 0); + } + return hashCode; + } + } + + } + #endregion + private static bool TryGetOpCode(short code, out OpCode opCode) { if (_opCodes != null) @@ -205,6 +269,41 @@ internal bool IsTypeRegistered(Type t) } #endregion + public AddItemToCollection GetCollectionAdder(Type collectionType) + { + var collType = typeof(IList).IsAssignableFrom(collectionType) ? typeof(IList) : collectionType; // Delegate the call to IList.Add(object) for all types that implement it. + if (_genericCollectionAdders.TryGetValue(collType, out var adder)) + return adder; + + var itemType = collType.IsGenericType ? GetGenericArguments(collType).SingleOrDefault() ?? typeof(object) : typeof(object); + var mi = collType.GetMethod(nameof(ICollection.Add), new[] {itemType}); + if (mi != null) + { + var inst = Expression.Parameter(typeof(object), "collection"); + var item = Expression.Parameter(typeof(object), "item"); + var itemArgument = itemType == item.Type ? item : Expression.Convert(item, itemType) as Expression; // Cast the item only if itemType is not object + adder = Expression.Lambda( + Expression.Call(Expression.Convert(inst, collType), mi, itemArgument), + inst, + item).Compile(); + } + _genericCollectionAdders[collType] = adder; + return adder; + } + + public Type GetGenericType(Type openType, params Type[] genericArguments) + { + Type result; + var key = new GenericTypeKey(openType, genericArguments); + if (_genericTypes.TryGetValue(key, out result)) + return result; + var args = key.GetParameters(); + System.Diagnostics.Debug.Assert(openType.GetGenericArguments().Length == args.Length); + result = openType.MakeGenericType(args); + _genericTypes.Add(key, result); + return result; + } + public Type GetGenericTypeDefinition(Type t) { Type tt = null; @@ -221,12 +320,12 @@ public Type GetGenericTypeDefinition(Type t) public Type[] GetGenericArguments(Type t) { Type[] tt = null; - if (_genericTypes.TryGetValue(t, out tt)) + if (_genericArguments.TryGetValue(t, out tt)) return tt; else { tt = t.GetGenericArguments(); - _genericTypes.Add(t, tt); + _genericArguments.Add(t, tt); return tt; } } @@ -851,8 +950,9 @@ internal void ClearReflectionCache() _constrcache = new SafeDictionary(10); _getterscache = new SafeDictionary(10); _propertycache = new SafeDictionary>(10); - _genericTypes = new SafeDictionary(10); + _genericArguments = new SafeDictionary(10); _genericTypeDef = new SafeDictionary(10); + _genericTypes = new SafeDictionary(10); } } }