Skip to content

Commit 172ec77

Browse files
author
rstam
committed
Implemented CSHARP-395. ExtraElements property or field can now be of type IDictionary<string, object> in addition to BsonDocument. Using IDictionary keeps your data model free of dependencies on the driver, and BSON values are mapped to equivalent .NET types when possible.
1 parent d66bce7 commit 172ec77

File tree

5 files changed

+286
-20
lines changed

5 files changed

+286
-20
lines changed

Bson/ObjectModel/BsonTypeMapper.cs

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ public static class BsonTypeMapper
5252
{ typeof(BsonString), Conversion.None },
5353
{ typeof(BsonSymbol), Conversion.None },
5454
{ typeof(BsonTimestamp), Conversion.None },
55+
{ typeof(BsonUndefined), Conversion.None },
5556
{ typeof(BsonValue), Conversion.None },
5657
{ typeof(byte), Conversion.ByteToBsonInt32 },
5758
{ typeof(byte[]), Conversion.ByteArrayToBsonBinary },
@@ -86,7 +87,9 @@ public static class BsonTypeMapper
8687
{ Mapping.FromTo(typeof(BsonJavaScript), BsonType.JavaScript), Conversion.None },
8788
{ Mapping.FromTo(typeof(BsonJavaScript), BsonType.JavaScriptWithScope), Conversion.BsonJavaScriptToBsonJavaScriptWithScope },
8889
{ Mapping.FromTo(typeof(BsonJavaScriptWithScope), BsonType.JavaScriptWithScope), Conversion.None },
90+
{ Mapping.FromTo(typeof(BsonMaxKey), BsonType.Boolean), Conversion.BsonMaxKeyToBsonBoolean },
8991
{ Mapping.FromTo(typeof(BsonMaxKey), BsonType.MaxKey), Conversion.None },
92+
{ Mapping.FromTo(typeof(BsonMinKey), BsonType.Boolean), Conversion.BsonMinKeyToBsonBoolean },
9093
{ Mapping.FromTo(typeof(BsonMinKey), BsonType.MinKey), Conversion.None },
9194
{ Mapping.FromTo(typeof(BsonNull), BsonType.Boolean), Conversion.BsonNullToBsonBoolean },
9295
{ Mapping.FromTo(typeof(BsonNull), BsonType.Null), Conversion.None },
@@ -95,6 +98,8 @@ public static class BsonTypeMapper
9598
{ Mapping.FromTo(typeof(BsonString), BsonType.String), Conversion.None },
9699
{ Mapping.FromTo(typeof(BsonSymbol), BsonType.Symbol), Conversion.None },
97100
{ Mapping.FromTo(typeof(BsonTimestamp), BsonType.Timestamp), Conversion.None },
101+
{ Mapping.FromTo(typeof(BsonUndefined), BsonType.Boolean), Conversion.BsonUndefinedToBsonBoolean },
102+
{ Mapping.FromTo(typeof(BsonUndefined), BsonType.Undefined), Conversion.None },
98103
{ Mapping.FromTo(typeof(byte), BsonType.Boolean), Conversion.ByteToBsonBoolean },
99104
{ Mapping.FromTo(typeof(byte), BsonType.Double), Conversion.ByteToBsonDouble },
100105
{ Mapping.FromTo(typeof(byte), BsonType.Int32), Conversion.ByteToBsonInt32 },
@@ -320,6 +325,11 @@ private static BsonValue Convert(object value, Conversion conversion)
320325
{
321326
// note: I expect this switch statement to be compiled using a jump table and therefore to be very efficient
322327
case Conversion.None: return (BsonValue)value;
328+
case Conversion.BsonJavaScriptToBsonJavaScriptWithScope: return BsonJavaScriptWithScope.Create(((BsonJavaScript)value).Code, new BsonDocument());
329+
case Conversion.BsonMaxKeyToBsonBoolean: return BsonBoolean.True;
330+
case Conversion.BsonMinKeyToBsonBoolean: return BsonBoolean.True;
331+
case Conversion.BsonNullToBsonBoolean: return BsonBoolean.False;
332+
case Conversion.BsonUndefinedToBsonBoolean: return BsonBoolean.False;
323333
case Conversion.ByteArrayToBsonBinary: return new BsonBinaryData((byte[])value);
324334
case Conversion.ByteArrayToBsonObjectId: return BsonObjectId.Create((byte[])value);
325335
case Conversion.ByteToBsonBoolean: return BsonBoolean.Create((byte)value != 0);
@@ -344,15 +354,12 @@ private static BsonValue Convert(object value, Conversion conversion)
344354
case Conversion.Int64ToBsonBoolean: return BsonBoolean.Create((long)value != 0);
345355
case Conversion.Int64ToBsonDouble: return new BsonDouble((double)(long)value);
346356
case Conversion.Int64ToBsonTimestamp: return new BsonTimestamp((long)value);
347-
case Conversion.BsonMaxKeyToBsonBoolean: return BsonBoolean.True;
348-
case Conversion.BsonMinKeyToBsonBoolean: return BsonBoolean.True;
349357
case Conversion.NewBsonBoolean: return BsonBoolean.Create((bool)value);
350358
case Conversion.NewBsonDouble: return new BsonDouble((double)value);
351359
case Conversion.NewBsonInt32: return BsonInt32.Create((int)value);
352360
case Conversion.NewBsonInt64: return new BsonInt64((long)value);
353361
case Conversion.NewBsonObjectId: return new BsonObjectId((ObjectId)value);
354362
case Conversion.NewBsonString: return new BsonString((string)value);
355-
case Conversion.BsonNullToBsonBoolean: return BsonBoolean.False;
356363
case Conversion.RegexToBsonRegularExpression: return new BsonRegularExpression((Regex)value);
357364
case Conversion.SByteToBsonBoolean: return BsonBoolean.Create((sbyte)value != 0);
358365
case Conversion.SByteToBsonDouble: return new BsonDouble((double)(sbyte)value);
@@ -395,6 +402,11 @@ private static BsonValue Convert(object value, Conversion conversion)
395402
private enum Conversion
396403
{
397404
None,
405+
BsonJavaScriptToBsonJavaScriptWithScope,
406+
BsonMaxKeyToBsonBoolean,
407+
BsonMinKeyToBsonBoolean,
408+
BsonNullToBsonBoolean,
409+
BsonUndefinedToBsonBoolean,
398410
ByteArrayToBsonBinary,
399411
ByteArrayToBsonObjectId,
400412
ByteToBsonBoolean,
@@ -419,16 +431,12 @@ private enum Conversion
419431
Int64ToBsonBoolean,
420432
Int64ToBsonDouble,
421433
Int64ToBsonTimestamp,
422-
BsonJavaScriptToBsonJavaScriptWithScope,
423-
BsonMaxKeyToBsonBoolean,
424-
BsonMinKeyToBsonBoolean,
425434
NewBsonBoolean,
426435
NewBsonDouble,
427436
NewBsonInt32,
428437
NewBsonInt64,
429438
NewBsonObjectId,
430439
NewBsonString,
431-
BsonNullToBsonBoolean,
432440
RegexToBsonRegularExpression,
433441
SByteToBsonBoolean,
434442
SByteToBsonDouble,

Bson/Serialization/Attributes/BsonExtraElementsAttribute.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
namespace MongoDB.Bson.Serialization.Attributes
2222
{
2323
/// <summary>
24-
/// Indicates that extra elements should be ignored when this class is deserialized.
24+
/// Indicates that this property or field will be used to hold any extra elements found during deserialization.
2525
/// </summary>
2626
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
2727
public class BsonExtraElementsAttribute : BsonSerializationOptionsAttribute

Bson/Serialization/BsonClassMap.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -878,9 +878,9 @@ public void SetExtraElementsMember(BsonMemberMap memberMap)
878878
var message = string.Format("Class {0} already has an extra elements member.", _classType.FullName);
879879
throw new InvalidOperationException(message);
880880
}
881-
if (memberMap.MemberType != typeof(BsonDocument))
881+
if (memberMap.MemberType != typeof(BsonDocument) && !typeof(IDictionary<string, object>).IsAssignableFrom(memberMap.MemberType))
882882
{
883-
var message = string.Format("Type of ExtraElements member must be BsonDocument.");
883+
var message = string.Format("Type of ExtraElements member must be BsonDocument or implement IDictionary<string, object>.");
884884
throw new InvalidOperationException(message);
885885
}
886886

Bson/Serialization/BsonClassMapSerializer.cs

Lines changed: 112 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -377,14 +377,35 @@ private void DeserializeExtraElement(
377377
string elementName,
378378
BsonMemberMap extraElementsMemberMap)
379379
{
380-
var extraElements = (BsonDocument)extraElementsMemberMap.Getter(obj);
381-
if (extraElements == null)
380+
if (extraElementsMemberMap.MemberType == typeof(BsonDocument))
382381
{
383-
extraElements = new BsonDocument();
384-
extraElementsMemberMap.Setter(obj, extraElements);
382+
var extraElements = (BsonDocument)extraElementsMemberMap.Getter(obj);
383+
if (extraElements == null)
384+
{
385+
extraElements = new BsonDocument();
386+
extraElementsMemberMap.Setter(obj, extraElements);
387+
}
388+
var bsonValue = BsonValue.ReadFrom(bsonReader);
389+
extraElements[elementName] = bsonValue;
390+
}
391+
else
392+
{
393+
var extraElements = (IDictionary<string, object>)extraElementsMemberMap.Getter(obj);
394+
if (extraElements == null)
395+
{
396+
if (extraElementsMemberMap.MemberType == typeof(IDictionary<string, object>))
397+
{
398+
extraElements = new Dictionary<string, object>();
399+
}
400+
else
401+
{
402+
extraElements = (IDictionary<string, object>)Activator.CreateInstance(extraElementsMemberMap.MemberType);
403+
}
404+
extraElementsMemberMap.Setter(obj, extraElements);
405+
}
406+
var bsonValue = BsonValue.ReadFrom(bsonReader);
407+
extraElements[elementName] = MapBsonValueToDotNetValue(bsonValue);
385408
}
386-
var value = BsonValue.ReadFrom(bsonReader);
387-
extraElements[elementName] = value;
388409
}
389410

390411
private void DeserializeMember(BsonReader bsonReader, object obj, BsonMemberMap memberMap)
@@ -415,14 +436,96 @@ private void DeserializeMember(BsonReader bsonReader, object obj, BsonMemberMap
415436
}
416437
}
417438

439+
private object MapBsonValueToDotNetValue(BsonValue value)
440+
{
441+
switch (value.BsonType)
442+
{
443+
case BsonType.Array:
444+
var bsonArray = value.AsBsonArray;
445+
var list = new List<object>(bsonArray.Count);
446+
foreach (var item in bsonArray)
447+
{
448+
list.Add(MapBsonValueToDotNetValue(item));
449+
}
450+
return list;
451+
case BsonType.Binary:
452+
var binaryData = value.AsBsonBinaryData;
453+
if (binaryData.SubType == BsonBinarySubType.Binary)
454+
{
455+
return binaryData.Bytes;
456+
}
457+
if (binaryData.SubType == BsonBinarySubType.UuidLegacy || binaryData.SubType == BsonBinarySubType.UuidStandard)
458+
{
459+
return binaryData.ToGuid();
460+
}
461+
return binaryData;
462+
case BsonType.Boolean:
463+
return value.AsBoolean;
464+
case BsonType.DateTime:
465+
var bsonDateTime = value.AsBsonDateTime;
466+
if (bsonDateTime.IsValidDateTime)
467+
{
468+
return bsonDateTime.AsDateTime;
469+
}
470+
else
471+
{
472+
return bsonDateTime;
473+
}
474+
case BsonType.Document:
475+
var bsonDocument = value.AsBsonDocument;
476+
var dictionary = new Dictionary<string, object>();
477+
foreach (var element in bsonDocument.Elements)
478+
{
479+
dictionary[element.Name] = MapBsonValueToDotNetValue(element.Value);
480+
}
481+
return dictionary;
482+
case BsonType.Double:
483+
return value.AsDouble;
484+
case BsonType.Int32:
485+
return value.AsInt32;
486+
case BsonType.Int64:
487+
return value.AsInt64;
488+
case BsonType.Null:
489+
return null;
490+
case BsonType.ObjectId:
491+
return value.AsObjectId;
492+
case BsonType.String:
493+
return value.AsString;
494+
default:
495+
return value; // just return BsonValue for types that have no .NET equivalent
496+
}
497+
}
498+
418499
private void SerializeExtraElements(BsonWriter bsonWriter, object obj, BsonMemberMap extraElementsMemberMap)
419500
{
420-
var extraElements = (BsonDocument)extraElementsMemberMap.Getter(obj);
501+
var extraElements = extraElementsMemberMap.Getter(obj);
421502
if (extraElements != null)
422503
{
423-
foreach (var element in extraElements)
504+
if (extraElementsMemberMap.MemberType == typeof(BsonDocument))
424505
{
425-
element.WriteTo(bsonWriter);
506+
var bsonDocument = (BsonDocument)extraElements;
507+
foreach (var element in bsonDocument)
508+
{
509+
element.WriteTo(bsonWriter);
510+
}
511+
}
512+
else
513+
{
514+
var dictionary = (IDictionary<string, object>)extraElements;
515+
foreach (var key in dictionary.Keys)
516+
{
517+
bsonWriter.WriteName(key);
518+
var value = dictionary[key];
519+
if (value == null)
520+
{
521+
bsonWriter.WriteNull();
522+
}
523+
else
524+
{
525+
var bsonValue = BsonTypeMapper.MapToBsonValue(dictionary[key]);
526+
bsonValue.WriteTo(bsonWriter);
527+
}
528+
}
426529
}
427530
}
428531
}

0 commit comments

Comments
 (0)