Skip to content

Commit

Permalink
Force class maps + dictionary implementations as class maps
Browse files Browse the repository at this point in the history
Force any class to be serialized as a BSON class map and, if they're
dictionary implementations, to serialize keys as regular BSON elements.
  • Loading branch information
simsateo authored and mfidemraizer committed Oct 17, 2015
1 parent 6157640 commit b6288d2
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 8 deletions.
1 change: 1 addition & 0 deletions src/MongoDB.Bson/MongoDB.Bson.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@
<Compile Include="Serialization\CreatorMapDelegateCompiler.cs" />
<Compile Include="Serialization\ExpressionVisitor.cs" />
<Compile Include="Serialization\BsonSerializerRegistry.cs" />
<Compile Include="Serialization\ForceAsBsonClassMapSerializationProvider.cs" />
<Compile Include="Serialization\IBsonDictionarySerializer.cs" />
<Compile Include="Serialization\IBsonPolymorphicSerializer.cs" />
<Compile Include="Serialization\IBsonSerializerRegistry.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
namespace MongoDB.Bson.Serialization
{
using System;
using System.Collections.Generic;
using System.Linq;

/// <summary>
/// Represents a BSON serialization provider which defines that some serializable types should be treated
/// as BSON class maps.
/// </summary>
/// <remarks>
/// Argumented types to be forced as BSON class maps can be either concrete or also base class and interface ones.
///
/// This serialization provider is useful when a class may implement a collection interface (for example, <see cref="System.Collections.Generic.IList{T}"/>)
/// because the domain requires the class to act as a collection, but in terms of serialization, it must be serialized as a regular
/// POCO class.
/// </remarks>
/// <example>
/// For example, given the following class:
///
/// <code language="c#">
/// public interface ISomeInterface { }
/// public class SomeImpl : ISomeInterface { }
/// </code>
///
/// This provider can be configured both to force any <codeInline>SomeImpl</codeInline> to be treated as
/// BSON class map and also any implementation of <codeInline>ISomeInterface</codeInline> can be configured as a
/// forced type to let any implementation be serialized as a BSON class map:
///
/// <code language="c#">
/// ForceAsBsonClassMapSerializationProvider provider = new ForceAsBsonClassMapSerializationProvider(typeof(SomeImpl));
///
/// // or
///
/// ForceAsBsonClassMapSerializationProvider provider = new ForceAsBsonClassMapSerializationProvider(typeof(ISomeInterface));
///
/// // or even both
///
/// ForceAsBsonClassMapSerializationProvider provider = new ForceAsBsonClassMapSerializationProvider(typeof(SomeImpl), typeof(ISomeInterface));
/// </code>
/// </example>
public sealed class ForceAsBsonClassMapSerializationProvider : BsonSerializationProviderBase
{
private readonly HashSet<Type> _forcedTypes;

/// <summary>
/// Constructor to give forced types as a type array.
/// </summary>
/// <param name="forcedTypes">The whole types to be forced as BSON class maps</param>
public ForceAsBsonClassMapSerializationProvider(params Type[] forcedTypes)
: this((IEnumerable<Type>)forcedTypes)
{
}

/// <summary>
/// Constructor to give forced types as a sequence of types.
/// </summary>
/// <param name="forcedTypes">The whole types to be forced as BSON class maps</param>
public ForceAsBsonClassMapSerializationProvider(IEnumerable<Type> forcedTypes)
{
if (forcedTypes == null || forcedTypes.Count() == 0)
throw new ArgumentException("Cannot configure a forced BSON class map serialization provider which contains no types to be forced as BSON class maps", "forcedTypes");
if (!forcedTypes.All(type => type.IsClass || type.IsInterface))
throw new ArgumentException("Forced types must be classes or interfaces");

_forcedTypes = new HashSet<Type>(forcedTypes);
}

/// <summary>
/// Gets a set of types to be forced as BSON class maps during their serialization.
/// </summary>
public HashSet<Type> ForcedTypes { get { return _forcedTypes; } }

/// <inheritdoc/>
public override IBsonSerializer GetSerializer(Type type, IBsonSerializerRegistry serializerRegistry)
{
// Forcing can happen either if type to be serialized is within forced type set, or if one of forced types
// is implemented or inherited by the given type.
if (ForcedTypes.Contains(type) || ForcedTypes.Any(forcedType => forcedType.IsAssignableFrom(type)))
{
BsonClassMapSerializationProvider bsonClassMapProvider = new BsonClassMapSerializationProvider();

return bsonClassMapProvider.GetSerializer(type);
}

return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ public TClass DeserializeClass(BsonDeserializationContext context)
}
}

var docDictionaryImpl = document as IDictionary<string, object>;
var discriminatorConvention = _classMap.GetDiscriminatorConvention();
var allMemberMaps = _classMap.AllMemberMaps;
var extraElementsMemberMapIndex = _classMap.ExtraElementsMemberMapIndex;
Expand Down Expand Up @@ -191,6 +192,16 @@ public TClass DeserializeClass(BsonDeserializationContext context)
}
memberMapBitArray[memberMapIndex >> 5] |= 1U << (memberMapIndex & 31);
}
else if(docDictionaryImpl != null)
{
// If the document itself implements IDictionary<TKey, TValue>, document to serialize could
// contain extra elements as document properties...
docDictionaryImpl.Add
(
elementName,
BsonTypeMapper.MapToDotNetValue(BsonValueSerializer.Instance.Deserialize(context))
);
}
else
{
if (elementName == discriminatorConvention.ElementName)
Expand All @@ -202,6 +213,7 @@ public TClass DeserializeClass(BsonDeserializationContext context)
if (extraElementsMemberMapIndex >= 0)
{
var extraElementsMemberMap = _classMap.ExtraElementsMemberMap;

if (document != null)
{
DeserializeExtraElementMember(context, document, elementName, extraElementsMemberMap);
Expand Down Expand Up @@ -564,6 +576,7 @@ private void SerializeClass(BsonSerializationContext context, BsonSerializationA
var bsonWriter = context.Writer;

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

bsonWriter.WriteStartDocument();

Expand Down Expand Up @@ -591,14 +604,39 @@ private void SerializeClass(BsonSerializationContext context, BsonSerializationA
SerializeMember(context, document, memberMap);
}


// It might happen that a class implements IDictionary<string, object>, so
// the keys can be also extra elements.
SerializeDictionary(context, document as IDictionary<string, object>, classElementNames);

bsonWriter.WriteEndDocument();
}

private void SerializeDictionary(BsonSerializationContext context, IDictionary<string, object> extraElements, HashSet<string> classElementNames = null)
{
if (extraElements != null && extraElements.Count > 0)
{
foreach (var key in classElementNames == null ?
extraElements.Keys : extraElements.Keys.Where(key => !classElementNames.Contains(key)))

{
context.Writer.WriteName(key);
var value = extraElements[key];
var bsonValue = BsonTypeMapper.MapToBsonValue(value);
BsonValueSerializer.Instance.Serialize(context, bsonValue);
}
}
}

private void SerializeExtraElements(BsonSerializationContext context, object obj, BsonMemberMap extraElementsMemberMap)
{
var bsonWriter = context.Writer;

var extraElements = extraElementsMemberMap.Getter(obj);

if (extraElements == null)
extraElements = obj as IDictionary<string, object>;

if (extraElements != null)
{
if (extraElementsMemberMap.MemberType == typeof(BsonDocument))
Expand All @@ -612,14 +650,7 @@ private void SerializeExtraElements(BsonSerializationContext context, object obj
}
else
{
var dictionary = (IDictionary<string, object>)extraElements;
foreach (var key in dictionary.Keys)
{
bsonWriter.WriteName(key);
var value = dictionary[key];
var bsonValue = BsonTypeMapper.MapToBsonValue(value);
BsonValueSerializer.Instance.Serialize(context, bsonValue);
}
SerializeDictionary(context, (IDictionary<string, object>)extraElements);
}
}
}
Expand Down

0 comments on commit b6288d2

Please sign in to comment.