-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
BsonMapper.Deserialize.cs
321 lines (270 loc) · 10.3 KB
/
BsonMapper.Deserialize.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
using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using static LiteDB.Constants;
namespace LiteDB
{
public partial class BsonMapper
{
#region Basic direct .NET convert types
// direct bson types
private HashSet<Type> _bsonTypes = new HashSet<Type>
{
typeof(String),
typeof(Int32),
typeof(Int64),
typeof(Boolean),
typeof(Guid),
typeof(DateTime),
typeof(Byte[]),
typeof(ObjectId),
typeof(Double),
typeof(Decimal)
};
// simple convert types
private HashSet<Type> _basicTypes = new HashSet<Type>
{
typeof(Int16),
typeof(UInt16),
typeof(UInt32),
typeof(Single),
typeof(Char),
typeof(Byte),
typeof(SByte)
};
#endregion
/// <summary>
/// Deserialize a BsonDocument to entity class
/// </summary>
public virtual object ToObject(Type type, BsonDocument doc)
{
if (doc == null) throw new ArgumentNullException(nameof(doc));
// if T is BsonDocument, just return them
if (type == typeof(BsonDocument)) return doc;
return this.Deserialize(type, doc);
}
/// <summary>
/// Deserialize a BsonDocument to entity class
/// </summary>
public virtual T ToObject<T>(BsonDocument doc)
{
return (T)this.ToObject(typeof(T), doc);
}
/// <summary>
/// Deserialize a BsonValue to .NET object typed in T
/// </summary>
public T Deserialize<T>(BsonValue value)
{
if (value == null) return default(T);
var result = this.Deserialize(typeof(T), value);
return (T)result;
}
/// <summary>
/// Deserilize a BsonValue to .NET object based on type parameter
/// </summary>
public object Deserialize(Type type, BsonValue value)
{
// null value - null returns
if (value.IsNull) return null;
// if is nullable, get underlying type
if (Reflection.IsNullable(type))
{
type = Reflection.UnderlyingTypeOf(type);
}
// test if has a custom type implementation
if (_customDeserializer.TryGetValue(type, out Func<BsonValue, object> custom))
{
return custom(value);
}
var typeInfo = type.GetTypeInfo();
// check if your type is already a BsonValue/BsonDocument/BsonArray
if (type == typeof(BsonValue))
{
return value;
}
else if (type == typeof(BsonDocument))
{
return value.AsDocument;
}
else if (type == typeof(BsonArray))
{
return value.AsArray;
}
// raw values to native bson values
else if (_bsonTypes.Contains(type))
{
return value.RawValue;
}
// simple ConvertTo to basic .NET types
else if (_basicTypes.Contains(type))
{
return Convert.ChangeType(value.RawValue, type);
}
// special cast to UInt64 to Int64
else if (type == typeof(UInt64))
{
return unchecked((UInt64)value.AsInt64);
}
// enum value is an int
else if (typeInfo.IsEnum)
{
if (value.IsString) return Enum.Parse(type, value.AsString);
if (value.IsNumber) return Enum.ToObject(type, value.AsInt32);
}
// if value is array, deserialize as array
else if (value.IsArray)
{
// when array are from an object (like in Dictionary<string, object> { ["array"] = new string[] { "a", "b" }
if (type == typeof(object))
{
return this.DeserializeArray(typeof(object), value.AsArray);
}
if (type.IsArray)
{
return this.DeserializeArray(type.GetElementType(), value.AsArray);
}
else
{
return this.DeserializeList(type, value.AsArray);
}
}
// if value is document, deserialize as document
else if (value.IsDocument)
{
// if type is anonymous use special handler
if (type.IsAnonymousType())
{
return this.DeserializeAnonymousType(type, value.AsDocument);
}
var doc = value.AsDocument;
// test if value is object and has _type
if (doc.TryGetValue("_type", out var typeField) && typeField.IsString)
{
var actualType = _typeNameBinder.GetType(typeField.AsString);
if (actualType == null) throw LiteException.InvalidTypedName(typeField.AsString);
// avoid initialize class that are not assignable
if (!type.IsAssignableFrom(actualType))
{
throw LiteException.DataTypeNotAssignable(type.FullName, actualType.FullName);
}
// avoid use of "System.Diagnostics.Process" in object type definition
// using String test to work in .netstandard 1.3
if (actualType.FullName.Equals("System.Diagnostics.Process", StringComparison.OrdinalIgnoreCase) &&
actualType.Assembly.GetName().Name.Equals("System", StringComparison.OrdinalIgnoreCase))
{
throw LiteException.AvoidUseOfProcess();
}
type = actualType;
}
// when complex type has no definition (== typeof(object)) use Dictionary<string, object> to better set values
else if (type == typeof(object))
{
type = typeof(Dictionary<string, object>);
}
var entity = this.GetEntityMapper(type);
// initialize CreateInstance
if (entity.CreateInstance == null)
{
entity.CreateInstance =
this.GetTypeCtor(entity) ??
((BsonDocument v) => Reflection.CreateInstance(entity.ForType));
}
var o = _typeInstantiator(type) ?? entity.CreateInstance(doc);
if (o is IDictionary dict)
{
if (o.GetType().GetTypeInfo().IsGenericType)
{
var k = type.GetGenericArguments()[0];
var t = type.GetGenericArguments()[1];
this.DeserializeDictionary(k, t, dict, value.AsDocument);
}
else
{
this.DeserializeDictionary(typeof(object), typeof(object), dict, value.AsDocument);
}
}
else
{
this.DeserializeObject(entity, o, doc);
}
return o;
}
// in last case, return value as-is - can cause "cast error"
// it's used for "public object MyInt { get; set; }"
return value.RawValue;
}
private object DeserializeArray(Type type, BsonArray array)
{
var arr = Array.CreateInstance(type, array.Count);
var idx = 0;
foreach (var item in array)
{
arr.SetValue(this.Deserialize(type, item), idx++);
}
return arr;
}
private object DeserializeList(Type type, BsonArray value)
{
var itemType = Reflection.GetListItemType(type);
var enumerable = (IEnumerable)Reflection.CreateInstance(type);
if (enumerable is IList list)
{
foreach (BsonValue item in value)
{
list.Add(this.Deserialize(itemType, item));
}
}
else
{
var addMethod = type.GetMethod("Add");
foreach (BsonValue item in value)
{
addMethod.Invoke(enumerable, new[] { this.Deserialize(itemType, item) });
}
}
return enumerable;
}
private void DeserializeDictionary(Type K, Type T, IDictionary dict, BsonDocument value)
{
var isKEnum = K.GetTypeInfo().IsEnum;
foreach (var el in value.GetElements())
{
var k = isKEnum ? Enum.Parse(K, el.Key) : K == typeof(Uri) ? new Uri(el.Key) : Convert.ChangeType(el.Key, K);
var v = this.Deserialize(T, el.Value);
dict.Add(k, v);
}
}
private void DeserializeObject(EntityMapper entity, object obj, BsonDocument value)
{
foreach (var member in entity.Members.Where(x => x.Setter != null))
{
if (value.TryGetValue(member.FieldName, out var val))
{
// check if has a custom deserialize function
if (member.Deserialize != null)
{
member.Setter(obj, member.Deserialize(val, this));
}
else
{
member.Setter(obj, this.Deserialize(member.DataType, val));
}
}
}
}
private object DeserializeAnonymousType(Type type, BsonDocument value)
{
var args = new List<object>();
var ctor = type.GetConstructors()[0];
foreach (var par in ctor.GetParameters())
{
var arg = this.Deserialize(par.ParameterType, value[par.Name]);
args.Add(arg);
}
var obj = Activator.CreateInstance(type, args.ToArray());
return obj;
}
}
}