forked from simplefx/Simple.OData
-
Notifications
You must be signed in to change notification settings - Fork 196
/
DictionaryExtensions.cs
367 lines (309 loc) · 10.7 KB
/
DictionaryExtensions.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
using System.Collections.Concurrent;
using System.Reflection;
using System.Runtime.CompilerServices;
namespace Simple.OData.Client.Extensions;
internal static class DictionaryExtensions
{
private static ConcurrentDictionary<Type, ActivatorDelegate> _defaultActivators = new();
private static ConcurrentDictionary<Tuple<Type, Type>, ActivatorDelegate> _collectionActivators = new();
internal static Func<IDictionary<string, object>, ITypeCache, ODataEntry> CreateDynamicODataEntry { get; set; }
internal static void ClearCache()
{
_defaultActivators = new ConcurrentDictionary<Type, ActivatorDelegate>();
_collectionActivators = new ConcurrentDictionary<Tuple<Type, Type>, ActivatorDelegate>();
}
public static T ToObject<T>(this IDictionary<string, object>? source, ITypeCache typeCache, bool dynamicObject = false)
where T : class
{
if (typeCache is null)
{
throw new ArgumentNullException(nameof(typeCache));
}
if (source is null)
{
return default(T);
}
if (typeCache.IsTypeAssignableFrom(typeof(IDictionary<string, object>), typeof(T)))
{
return source as T;
}
if (typeof(T) == typeof(ODataEntry))
{
return CreateODataEntry(source, typeCache, dynamicObject) as T;
}
if (typeof(T) == typeof(string) || typeCache.IsValue(typeof(T)))
{
throw new InvalidOperationException($"Unable to convert structural data to {typeof(T).Name}.");
}
return (T)ToObject(source, typeCache, typeof(T), dynamicObject);
}
public static object? ToObject(
this IDictionary<string, object> source,
ITypeCache typeCache,
Type type,
bool dynamicObject = false)
{
if (typeCache is null)
{
throw new ArgumentNullException(nameof(typeCache));
}
if (source is null)
{
return null;
}
if (typeCache.IsTypeAssignableFrom(typeof(IDictionary<string, object>), type))
{
return source;
}
if (type == typeof(ODataEntry))
{
return CreateODataEntry(source, typeCache, dynamicObject);
}
// Check before custom converter so we use the most appropriate type.
if (source.ContainsKey(FluentCommand.AnnotationsLiteral))
{
type = GetTypeFromAnnotation(source, typeCache, type);
}
if (typeCache.Converter.HasDictionaryConverter(type))
{
return typeCache.Converter.Convert(source, type);
}
if (type.HasCustomAttribute(typeof(CompilerGeneratedAttribute), false))
{
return CreateInstanceOfAnonymousType(source, type, typeCache);
}
var instance = CreateInstance(type);
IDictionary<string, object>? dynamicProperties = null;
var dynamicPropertiesContainerName = typeCache.DynamicContainerName(type);
if (!string.IsNullOrEmpty(dynamicPropertiesContainerName))
{
dynamicProperties = CreateDynamicPropertiesContainer(type, typeCache, instance, dynamicPropertiesContainerName);
}
foreach (var item in source)
{
var property = FindMatchingProperty(type, typeCache, item);
if (property is not null && property.CanWrite)
{
if (item.Value is not null)
{
property.SetValue(instance, ConvertValue(property.PropertyType, typeCache, item.Value), null);
}
}
else
{
dynamicProperties?.Add(item.Key, item.Value);
}
}
return instance;
}
private static Type GetTypeFromAnnotation(IDictionary<string, object> source, ITypeCache typeCache, Type type)
{
var annotations = source[FluentCommand.AnnotationsLiteral] as ODataEntryAnnotations;
var odataType = annotations?.TypeName;
if (string.IsNullOrEmpty(odataType))
{
return type;
}
// TODO: We could cast the ITypeCatcher to TypeCache and use it's property but it's a bit naughty - conditional?
var resolver = ODataNameMatchResolver.NotStrict;
if (!resolver.IsMatch(odataType, type.Name))
{
// OK, something other than the base type, see if we can match it
var derived = typeCache.GetDerivedTypes(type).FirstOrDefault(x => resolver.IsMatch(odataType, typeCache.GetMappedName(x)));
if (derived is not null)
{
return derived;
}
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
var typeFound = assembly.GetType(odataType);
if (typeFound is not null)
{
return typeFound;
}
}
// TODO: Should we throw an exception here or log a warning here as we don't understand the data
}
return type;
}
private static PropertyInfo FindMatchingProperty(Type type, ITypeCache typeCache, KeyValuePair<string, object> item)
{
var property = typeCache.GetMappedProperty(type, item.Key);
if (property is null && item.Key == FluentCommand.AnnotationsLiteral)
{
property = typeCache.GetAnnotationsProperty(type);
}
return property;
}
private static object ConvertValue(Type type, ITypeCache typeCache, object itemValue)
{
return IsCollectionType(type, typeCache, itemValue)
? ConvertCollection(type, typeCache, itemValue)
: ConvertSingle(type, typeCache, itemValue);
}
private static bool IsCollectionType(Type type, ITypeCache typeCache, object itemValue)
{
return
(type.IsArray || typeCache.IsGeneric(type) &&
typeCache.IsTypeAssignableFrom(typeof(System.Collections.IEnumerable), type)) &&
(itemValue as System.Collections.IEnumerable) is not null;
}
private static bool IsCompoundType(Type type, ITypeCache typeCache)
{
return !typeCache.IsValue(type) && !type.IsArray && type != typeof(string);
}
private static object ConvertEnum(Type type, ITypeCache typeCache, object itemValue)
{
if (itemValue is null)
{
return null;
}
var stringValue = itemValue.ToString();
if (int.TryParse(stringValue, out var intValue))
{
typeCache.TryConvert(intValue, type, out var result);
return result;
}
else
{
return Enum.Parse(type, stringValue, false);
}
}
private static object ConvertSingle(Type type, ITypeCache typeCache, object itemValue)
{
object TryConvert(object v, Type t) => typeCache.TryConvert(v, t, out var result) ? result : v;
return type == typeof(ODataEntryAnnotations)
? itemValue
: IsCompoundType(type, typeCache)
? itemValue.ToDictionary(typeCache).ToObject(typeCache, type)
: type.IsEnumType()
? ConvertEnum(type, typeCache, itemValue)
: TryConvert(itemValue, type);
}
private static object ConvertCollection(Type type, ITypeCache typeCache, object itemValue)
{
var elementType = type.IsArray
? type.GetElementType()
: typeCache.IsGeneric(type) && typeCache.GetGenericTypeArguments(type).Length == 1
? typeCache.GetGenericTypeArguments(type)[0]
: null;
if (elementType is null)
{
return null;
}
var count = GetCollectionCount(itemValue);
var arrayValue = Array.CreateInstance(elementType, count);
count = 0;
foreach (var item in (itemValue as System.Collections.IEnumerable))
{
arrayValue.SetValue(ConvertSingle(elementType, typeCache, item), count++);
}
if (type.IsArray || typeCache.IsTypeAssignableFrom(type, arrayValue.GetType()))
{
return arrayValue;
}
else
{
var collectionTypes = new[]
{
typeof(IList<>).MakeGenericType(elementType),
typeof(IEnumerable<>).MakeGenericType(elementType)
};
var collectionType = type.GetConstructor([collectionTypes[0]]) is not null
? collectionTypes[0]
: collectionTypes[1];
var activator = _collectionActivators.GetOrAdd(new Tuple<Type, Type>(type, collectionType), t => type.CreateActivator(collectionType));
return activator?.Invoke(arrayValue);
}
}
private static int GetCollectionCount(object collection)
{
if (collection is System.Collections.IList list)
{
return list.Count;
}
else if (collection is System.Collections.IDictionary dictionary)
{
return dictionary.Count;
}
else
{
var count = 0;
var e = ((System.Collections.IEnumerable)collection).GetEnumerator();
using (e as IDisposable)
{
while (e.MoveNext())
{
count++;
}
}
return count;
}
}
public static IDictionary<string, object> ToDictionary(this object source, ITypeCache typeCache)
{
if (source is null)
{
return new Dictionary<string, object>();
}
if (source is IDictionary<string, object> objects)
{
return objects;
}
if (source is ODataEntry entry)
{
return (Dictionary<string, object>)entry;
}
return typeCache.ToDictionary(source);
}
private static object CreateInstance(Type type)
{
if (type == typeof(IDictionary<string, object>))
{
return new Dictionary<string, object>();
}
else
{
var ctor = _defaultActivators.GetOrAdd(type, t => t.CreateActivator());
return ctor.Invoke();
}
}
private static ODataEntry CreateODataEntry(IDictionary<string, object> source, ITypeCache typeCache, bool dynamicObject = false)
{
return dynamicObject && CreateDynamicODataEntry is not null ?
CreateDynamicODataEntry(source, typeCache) :
new ODataEntry(source);
}
private static IDictionary<string, object> CreateDynamicPropertiesContainer(Type type, ITypeCache typeCache, object instance, string dynamicPropertiesContainerName)
{
var property = typeCache.GetNamedProperty(type, dynamicPropertiesContainerName) ?? throw new ArgumentException($"Type {type} does not have property {dynamicPropertiesContainerName} ");
if (!typeCache.IsTypeAssignableFrom(typeof(IDictionary<string, object>), property.PropertyType))
{
throw new InvalidOperationException($"Property {dynamicPropertiesContainerName} must implement IDictionary<string,object> interface");
}
var dynamicProperties = new Dictionary<string, object>();
property.SetValue(instance, dynamicProperties, null);
return dynamicProperties;
}
private static object CreateInstanceOfAnonymousType(IDictionary<string, object> source, Type type, ITypeCache typeCache)
{
var constructor = FindConstructorOfAnonymousType(type, source) ?? throw new ConstructorNotFoundException(type, source.Values.Select(v => v.GetType()));
var parameterInfos = constructor.GetParameters();
var constructorParameters = new object[parameterInfos.Length];
for (var parameterIndex = 0; parameterIndex < parameterInfos.Length; parameterIndex++)
{
var parameterInfo = parameterInfos[parameterIndex];
constructorParameters[parameterIndex] = ConvertValue(parameterInfo.ParameterType, typeCache, source[parameterInfo.Name]);
}
return constructor.Invoke(constructorParameters);
}
private static ConstructorInfo FindConstructorOfAnonymousType(Type type, IDictionary<string, object> source)
{
return type.GetDeclaredConstructors().FirstOrDefault(c =>
{
var parameters = c.GetParameters();
return parameters.Length == source.Count &&
parameters.All(p => source.ContainsKey(p.Name));
});
}
}