Skip to content

Commit b6288d2

Browse files
simsateomfidemraizer
authored andcommittedOct 17, 2015
Force class maps + dictionary implementations as class maps
Force any class to be serialized as a BSON class map and, if they're dictionary implementations, to serialize keys as regular BSON elements.
1 parent 6157640 commit b6288d2

File tree

3 files changed

+129
-8
lines changed

3 files changed

+129
-8
lines changed
 

‎src/MongoDB.Bson/MongoDB.Bson.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@
187187
<Compile Include="Serialization\CreatorMapDelegateCompiler.cs" />
188188
<Compile Include="Serialization\ExpressionVisitor.cs" />
189189
<Compile Include="Serialization\BsonSerializerRegistry.cs" />
190+
<Compile Include="Serialization\ForceAsBsonClassMapSerializationProvider.cs" />
190191
<Compile Include="Serialization\IBsonDictionarySerializer.cs" />
191192
<Compile Include="Serialization\IBsonPolymorphicSerializer.cs" />
192193
<Compile Include="Serialization\IBsonSerializerRegistry.cs" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
namespace MongoDB.Bson.Serialization
2+
{
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
7+
/// <summary>
8+
/// Represents a BSON serialization provider which defines that some serializable types should be treated
9+
/// as BSON class maps.
10+
/// </summary>
11+
/// <remarks>
12+
/// Argumented types to be forced as BSON class maps can be either concrete or also base class and interface ones.
13+
///
14+
/// This serialization provider is useful when a class may implement a collection interface (for example, <see cref="System.Collections.Generic.IList{T}"/>)
15+
/// because the domain requires the class to act as a collection, but in terms of serialization, it must be serialized as a regular
16+
/// POCO class.
17+
/// </remarks>
18+
/// <example>
19+
/// For example, given the following class:
20+
///
21+
/// <code language="c#">
22+
/// public interface ISomeInterface { }
23+
/// public class SomeImpl : ISomeInterface { }
24+
/// </code>
25+
///
26+
/// This provider can be configured both to force any <codeInline>SomeImpl</codeInline> to be treated as
27+
/// BSON class map and also any implementation of <codeInline>ISomeInterface</codeInline> can be configured as a
28+
/// forced type to let any implementation be serialized as a BSON class map:
29+
///
30+
/// <code language="c#">
31+
/// ForceAsBsonClassMapSerializationProvider provider = new ForceAsBsonClassMapSerializationProvider(typeof(SomeImpl));
32+
///
33+
/// // or
34+
///
35+
/// ForceAsBsonClassMapSerializationProvider provider = new ForceAsBsonClassMapSerializationProvider(typeof(ISomeInterface));
36+
///
37+
/// // or even both
38+
///
39+
/// ForceAsBsonClassMapSerializationProvider provider = new ForceAsBsonClassMapSerializationProvider(typeof(SomeImpl), typeof(ISomeInterface));
40+
/// </code>
41+
/// </example>
42+
public sealed class ForceAsBsonClassMapSerializationProvider : BsonSerializationProviderBase
43+
{
44+
private readonly HashSet<Type> _forcedTypes;
45+
46+
/// <summary>
47+
/// Constructor to give forced types as a type array.
48+
/// </summary>
49+
/// <param name="forcedTypes">The whole types to be forced as BSON class maps</param>
50+
public ForceAsBsonClassMapSerializationProvider(params Type[] forcedTypes)
51+
: this((IEnumerable<Type>)forcedTypes)
52+
{
53+
}
54+
55+
/// <summary>
56+
/// Constructor to give forced types as a sequence of types.
57+
/// </summary>
58+
/// <param name="forcedTypes">The whole types to be forced as BSON class maps</param>
59+
public ForceAsBsonClassMapSerializationProvider(IEnumerable<Type> forcedTypes)
60+
{
61+
if (forcedTypes == null || forcedTypes.Count() == 0)
62+
throw new ArgumentException("Cannot configure a forced BSON class map serialization provider which contains no types to be forced as BSON class maps", "forcedTypes");
63+
if (!forcedTypes.All(type => type.IsClass || type.IsInterface))
64+
throw new ArgumentException("Forced types must be classes or interfaces");
65+
66+
_forcedTypes = new HashSet<Type>(forcedTypes);
67+
}
68+
69+
/// <summary>
70+
/// Gets a set of types to be forced as BSON class maps during their serialization.
71+
/// </summary>
72+
public HashSet<Type> ForcedTypes { get { return _forcedTypes; } }
73+
74+
/// <inheritdoc/>
75+
public override IBsonSerializer GetSerializer(Type type, IBsonSerializerRegistry serializerRegistry)
76+
{
77+
// Forcing can happen either if type to be serialized is within forced type set, or if one of forced types
78+
// is implemented or inherited by the given type.
79+
if (ForcedTypes.Contains(type) || ForcedTypes.Any(forcedType => forcedType.IsAssignableFrom(type)))
80+
{
81+
BsonClassMapSerializationProvider bsonClassMapProvider = new BsonClassMapSerializationProvider();
82+
83+
return bsonClassMapProvider.GetSerializer(type);
84+
}
85+
86+
return null;
87+
}
88+
}
89+
}

‎src/MongoDB.Bson/Serialization/Serializers/BsonClassMapSerializer.cs

+39-8
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ public TClass DeserializeClass(BsonDeserializationContext context)
142142
}
143143
}
144144

145+
var docDictionaryImpl = document as IDictionary<string, object>;
145146
var discriminatorConvention = _classMap.GetDiscriminatorConvention();
146147
var allMemberMaps = _classMap.AllMemberMaps;
147148
var extraElementsMemberMapIndex = _classMap.ExtraElementsMemberMapIndex;
@@ -191,6 +192,16 @@ public TClass DeserializeClass(BsonDeserializationContext context)
191192
}
192193
memberMapBitArray[memberMapIndex >> 5] |= 1U << (memberMapIndex & 31);
193194
}
195+
else if(docDictionaryImpl != null)
196+
{
197+
// If the document itself implements IDictionary<TKey, TValue>, document to serialize could
198+
// contain extra elements as document properties...
199+
docDictionaryImpl.Add
200+
(
201+
elementName,
202+
BsonTypeMapper.MapToDotNetValue(BsonValueSerializer.Instance.Deserialize(context))
203+
);
204+
}
194205
else
195206
{
196207
if (elementName == discriminatorConvention.ElementName)
@@ -202,6 +213,7 @@ public TClass DeserializeClass(BsonDeserializationContext context)
202213
if (extraElementsMemberMapIndex >= 0)
203214
{
204215
var extraElementsMemberMap = _classMap.ExtraElementsMemberMap;
216+
205217
if (document != null)
206218
{
207219
DeserializeExtraElementMember(context, document, elementName, extraElementsMemberMap);
@@ -564,6 +576,7 @@ private void SerializeClass(BsonSerializationContext context, BsonSerializationA
564576
var bsonWriter = context.Writer;
565577

566578
var remainingMemberMaps = _classMap.AllMemberMaps.ToList();
579+
HashSet<string> classElementNames = new HashSet<string>(remainingMemberMaps.Select(map => map.MemberName));
567580

568581
bsonWriter.WriteStartDocument();
569582

@@ -591,14 +604,39 @@ private void SerializeClass(BsonSerializationContext context, BsonSerializationA
591604
SerializeMember(context, document, memberMap);
592605
}
593606

607+
608+
// It might happen that a class implements IDictionary<string, object>, so
609+
// the keys can be also extra elements.
610+
SerializeDictionary(context, document as IDictionary<string, object>, classElementNames);
611+
594612
bsonWriter.WriteEndDocument();
595613
}
596614

615+
private void SerializeDictionary(BsonSerializationContext context, IDictionary<string, object> extraElements, HashSet<string> classElementNames = null)
616+
{
617+
if (extraElements != null && extraElements.Count > 0)
618+
{
619+
foreach (var key in classElementNames == null ?
620+
extraElements.Keys : extraElements.Keys.Where(key => !classElementNames.Contains(key)))
621+
622+
{
623+
context.Writer.WriteName(key);
624+
var value = extraElements[key];
625+
var bsonValue = BsonTypeMapper.MapToBsonValue(value);
626+
BsonValueSerializer.Instance.Serialize(context, bsonValue);
627+
}
628+
}
629+
}
630+
597631
private void SerializeExtraElements(BsonSerializationContext context, object obj, BsonMemberMap extraElementsMemberMap)
598632
{
599633
var bsonWriter = context.Writer;
600634

601635
var extraElements = extraElementsMemberMap.Getter(obj);
636+
637+
if (extraElements == null)
638+
extraElements = obj as IDictionary<string, object>;
639+
602640
if (extraElements != null)
603641
{
604642
if (extraElementsMemberMap.MemberType == typeof(BsonDocument))
@@ -612,14 +650,7 @@ private void SerializeExtraElements(BsonSerializationContext context, object obj
612650
}
613651
else
614652
{
615-
var dictionary = (IDictionary<string, object>)extraElements;
616-
foreach (var key in dictionary.Keys)
617-
{
618-
bsonWriter.WriteName(key);
619-
var value = dictionary[key];
620-
var bsonValue = BsonTypeMapper.MapToBsonValue(value);
621-
BsonValueSerializer.Instance.Serialize(context, bsonValue);
622-
}
653+
SerializeDictionary(context, (IDictionary<string, object>)extraElements);
623654
}
624655
}
625656
}

0 commit comments

Comments
 (0)
Failed to load comments.