From c3c4e99ddaa4b9145d7764ab52656a2b91f71fbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Fidemraizer?= Date: Fri, 16 Oct 2015 11:53:36 +0200 Subject: [PATCH] Now classes which implement collection interfaces can still be serialized as BSON class maps. In addition, if the class implements IDictionary, the keys that don't correspond to a regular class property will be both serialized and deserialized as regular BSON elements/values. --- src/MongoDB.Bson/MongoDB.Bson.csproj | 1 + ...orceAsBsonClassMapSerializationProvider.cs | 89 +++++++++++++++++++ .../Serializers/BsonClassMapSerializer.cs | 75 ++++++++++++++-- 3 files changed, 156 insertions(+), 9 deletions(-) create mode 100644 src/MongoDB.Bson/Serialization/ForceAsBsonClassMapSerializationProvider.cs diff --git a/src/MongoDB.Bson/MongoDB.Bson.csproj b/src/MongoDB.Bson/MongoDB.Bson.csproj index c7aa01084e0..a23d6d6ccf3 100644 --- a/src/MongoDB.Bson/MongoDB.Bson.csproj +++ b/src/MongoDB.Bson/MongoDB.Bson.csproj @@ -187,6 +187,7 @@ + diff --git a/src/MongoDB.Bson/Serialization/ForceAsBsonClassMapSerializationProvider.cs b/src/MongoDB.Bson/Serialization/ForceAsBsonClassMapSerializationProvider.cs new file mode 100644 index 00000000000..d7abc634ad9 --- /dev/null +++ b/src/MongoDB.Bson/Serialization/ForceAsBsonClassMapSerializationProvider.cs @@ -0,0 +1,89 @@ +namespace MongoDB.Bson.Serialization +{ + using System; + using System.Collections.Generic; + using System.Linq; + + /// + /// Represents a BSON serialization provider which defines that some serializable types should be treated + /// as BSON class maps. + /// + /// + /// 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, ) + /// 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. + /// + /// + /// For example, given the following class: + /// + /// + /// public interface ISomeInterface { } + /// public class SomeImpl : ISomeInterface { } + /// + /// + /// This provider can be configured both to force any SomeImpl to be treated as + /// BSON class map and also any implementation of ISomeInterface can be configured as a + /// forced type to let any implementation be serialized as a BSON class map: + /// + /// + /// ForceAsBsonClassMapSerializationProvider provider = new ForceAsBsonClassMapSerializationProvider(typeof(SomeImpl)); + /// + /// // or + /// + /// ForceAsBsonClassMapSerializationProvider provider = new ForceAsBsonClassMapSerializationProvider(typeof(ISomeInterface)); + /// + /// // or even both + /// + /// ForceAsBsonClassMapSerializationProvider provider = new ForceAsBsonClassMapSerializationProvider(typeof(SomeImpl), typeof(ISomeInterface)); + /// + /// + public sealed class ForceAsBsonClassMapSerializationProvider : BsonSerializationProviderBase + { + private readonly HashSet _forcedTypes; + + /// + /// Constructor to give forced types as a type array. + /// + /// The whole types to be forced as BSON class maps + public ForceAsBsonClassMapSerializationProvider(params Type[] forcedTypes) + : this((IEnumerable)forcedTypes) + { + } + + /// + /// Constructor to give forced types as a sequence of types. + /// + /// The whole types to be forced as BSON class maps + public ForceAsBsonClassMapSerializationProvider(IEnumerable 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(forcedTypes); + } + + /// + /// Gets a set of types to be forced as BSON class maps during their serialization. + /// + public HashSet ForcedTypes { get { return _forcedTypes; } } + + /// + 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; + } + } +} \ No newline at end of file diff --git a/src/MongoDB.Bson/Serialization/Serializers/BsonClassMapSerializer.cs b/src/MongoDB.Bson/Serialization/Serializers/BsonClassMapSerializer.cs index 5914baa67d8..12f1fc9b9a5 100644 --- a/src/MongoDB.Bson/Serialization/Serializers/BsonClassMapSerializer.cs +++ b/src/MongoDB.Bson/Serialization/Serializers/BsonClassMapSerializer.cs @@ -142,6 +142,7 @@ public TClass DeserializeClass(BsonDeserializationContext context) } } + var docDictionaryImpl = document as IDictionary; var discriminatorConvention = _classMap.GetDiscriminatorConvention(); var allMemberMaps = _classMap.AllMemberMaps; var extraElementsMemberMapIndex = _classMap.ExtraElementsMemberMapIndex; @@ -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, document to serialize could + // contain extra elements as document properties... + docDictionaryImpl.Add + ( + elementName, + BsonTypeMapper.MapToDotNetValue(BsonValueSerializer.Instance.Deserialize(context)) + ); + } else { if (elementName == discriminatorConvention.ElementName) @@ -202,7 +213,9 @@ public TClass DeserializeClass(BsonDeserializationContext context) if (extraElementsMemberMapIndex >= 0) { var extraElementsMemberMap = _classMap.ExtraElementsMemberMap; - if (document != null) + var documentDictionaryImpl = document as IDictionary; + + if (document != null || documentDictionaryImpl != null) { DeserializeExtraElementMember(context, document, elementName, extraElementsMemberMap); } @@ -450,6 +463,31 @@ private TClass CreateInstanceUsingCreator(Dictionary values) return (TClass)document; } + private void X(BsonMemberMap extraElementsMemberMap, object obj) + { + + //var extraElements = (IDictionary)extraElementsMemberMap.Getter(obj); + + //if (extraElements == null) + // extraElements = obj as IDictionary; + + //if (extraElements == null) + //{ + // if (extraElementsMemberMap.MemberType == typeof(IDictionary)) + // { + // extraElements = new Dictionary(); + // } + // else + // { + // extraElements = (IDictionary)Activator.CreateInstance(extraElementsMemberMap.MemberType); + // } + // extraElementsMemberMap.Setter(obj, extraElements); + //} + + //var bsonValue = BsonValueSerializer.Instance.Deserialize(context); + //extraElements[elementName] = BsonTypeMapper.MapToDotNetValue(bsonValue); + } + private void DeserializeExtraElementMember( BsonDeserializationContext context, object obj, @@ -564,6 +602,7 @@ private void SerializeClass(BsonSerializationContext context, BsonSerializationA var bsonWriter = context.Writer; var remainingMemberMaps = _classMap.AllMemberMaps.ToList(); + HashSet classElementNames = new HashSet(remainingMemberMaps.Select(map => map.MemberName)); bsonWriter.WriteStartDocument(); @@ -591,14 +630,39 @@ private void SerializeClass(BsonSerializationContext context, BsonSerializationA SerializeMember(context, document, memberMap); } + + // It might happen that a class implements IDictionary, so + // the keys can be also extra elements. + SerializeDictionary(context, document as IDictionary, classElementNames); + bsonWriter.WriteEndDocument(); } + private void SerializeDictionary(BsonSerializationContext context, IDictionary extraElements, HashSet 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; + if (extraElements != null) { if (extraElementsMemberMap.MemberType == typeof(BsonDocument)) @@ -612,14 +676,7 @@ private void SerializeExtraElements(BsonSerializationContext context, object obj } else { - var dictionary = (IDictionary)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)extraElements); } } }