/* Copyright 2010-present MongoDB Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System; using System.Collections; using System.Collections.Generic; using System.Linq.Expressions; using MongoDB.Bson.IO; using MongoDB.Bson.Serialization.Options; namespace MongoDB.Bson.Serialization.Serializers { /// /// Represents a serializer for dictionaries. /// /// The type of the dictionary. public abstract class DictionarySerializerBase : ClassSerializerBase, IBsonDocumentSerializer, IBsonDictionarySerializer where TDictionary : class, IDictionary { // private constants private static class Flags { public const long Key = 1; public const long Value = 2; } // private fields private readonly DictionaryRepresentation _dictionaryRepresentation; private readonly SerializerHelper _helper; private readonly IBsonSerializer _keySerializer; private readonly IBsonSerializer _valueSerializer; // constructors /// /// Initializes a new instance of the class. /// public DictionarySerializerBase() : this(DictionaryRepresentation.Document) { } /// /// Initializes a new instance of the class. /// /// The dictionary representation. public DictionarySerializerBase(DictionaryRepresentation dictionaryRepresentation) : this(dictionaryRepresentation, new ObjectSerializer(), new ObjectSerializer()) { } /// /// Initializes a new instance of the class. /// /// The dictionary representation. /// The key serializer. /// The value serializer. public DictionarySerializerBase(DictionaryRepresentation dictionaryRepresentation, IBsonSerializer keySerializer, IBsonSerializer valueSerializer) { _dictionaryRepresentation = dictionaryRepresentation; _keySerializer = keySerializer; _valueSerializer = valueSerializer; _helper = new SerializerHelper ( new SerializerHelper.Member("k", Flags.Key), new SerializerHelper.Member("v", Flags.Value) ); } // public properties /// /// Gets the dictionary representation. /// /// /// The dictionary representation. /// public DictionaryRepresentation DictionaryRepresentation { get { return _dictionaryRepresentation; } } /// /// Gets the key serializer. /// /// /// The key serializer. /// public IBsonSerializer KeySerializer { get { return _keySerializer; } } /// /// Gets the value serializer. /// /// /// The value serializer. /// public IBsonSerializer ValueSerializer { get { return _valueSerializer; } } // public methods /// public bool TryGetMemberSerializationInfo(string memberName, out BsonSerializationInfo serializationInfo) { if (_dictionaryRepresentation != DictionaryRepresentation.Document) { serializationInfo = null; return false; } serializationInfo = new BsonSerializationInfo( memberName, _valueSerializer, _valueSerializer.ValueType); return true; } // protected methods /// /// Deserializes a value. /// /// The deserialization context. /// The deserialization args. /// A deserialized value. protected override TDictionary DeserializeValue(BsonDeserializationContext context, BsonDeserializationArgs args) { var bsonReader = context.Reader; var bsonType = bsonReader.GetCurrentBsonType(); switch (bsonType) { case BsonType.Array: return DeserializeArrayRepresentation(context); case BsonType.Document: return DeserializeDocumentRepresentation(context); default: throw CreateCannotDeserializeFromBsonTypeException(bsonType); } } /// /// Serializes a value. /// /// The serialization context. /// The serialization args. /// The object. protected override void SerializeValue(BsonSerializationContext context, BsonSerializationArgs args, TDictionary value) { var bsonWriter = context.Writer; switch (_dictionaryRepresentation) { case DictionaryRepresentation.Document: SerializeDocumentRepresentation(context, value); break; case DictionaryRepresentation.ArrayOfArrays: SerializeArrayOfArraysRepresentation(context, value); break; case DictionaryRepresentation.ArrayOfDocuments: SerializeArrayOfDocumentsRepresentation(context, value); break; default: var message = string.Format("'{0}' is not a valid IDictionary representation.", _dictionaryRepresentation); throw new BsonSerializationException(message); } } // protected methods /// /// Creates the instance. /// /// The instance. protected abstract TDictionary CreateInstance(); // private methods private TDictionary DeserializeArrayRepresentation(BsonDeserializationContext context) { var dictionary = CreateInstance(); var bsonReader = context.Reader; bsonReader.ReadStartArray(); while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) { object key; object value; var bsonType = bsonReader.GetCurrentBsonType(); switch (bsonType) { case BsonType.Array: bsonReader.ReadStartArray(); key = _keySerializer.Deserialize(context); value = _valueSerializer.Deserialize(context); bsonReader.ReadEndArray(); break; case BsonType.Document: key = null; value = null; _helper.DeserializeMembers(context, (elementName, flag) => { switch (flag) { case Flags.Key: key = _keySerializer.Deserialize(context); break; case Flags.Value: value = _valueSerializer.Deserialize(context); break; } }); break; default: throw CreateCannotDeserializeFromBsonTypeException(bsonType); } dictionary.Add(key, value); } bsonReader.ReadEndArray(); return dictionary; } private TDictionary DeserializeDocumentRepresentation(BsonDeserializationContext context) { var dictionary = CreateInstance(); var bsonReader = context.Reader; bsonReader.ReadStartDocument(); while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) { var key = DeserializeKeyString(bsonReader.ReadName()); var value = _valueSerializer.Deserialize(context); dictionary.Add(key, value); } bsonReader.ReadEndDocument(); return dictionary; } private object DeserializeKeyString(string keyString) { var keyDocument = new BsonDocument("k", keyString); using (var keyReader = new BsonDocumentReader(keyDocument)) { var context = BsonDeserializationContext.CreateRoot(keyReader); keyReader.ReadStartDocument(); keyReader.ReadName("k"); var key = _keySerializer.Deserialize(context); keyReader.ReadEndDocument(); return key; } } private void SerializeArrayOfArraysRepresentation(BsonSerializationContext context, TDictionary value) { var bsonWriter = context.Writer; bsonWriter.WriteStartArray(); foreach (DictionaryEntry dictionaryEntry in value) { bsonWriter.WriteStartArray(); _keySerializer.Serialize(context, dictionaryEntry.Key); _valueSerializer.Serialize(context, dictionaryEntry.Value); bsonWriter.WriteEndArray(); } bsonWriter.WriteEndArray(); } private void SerializeArrayOfDocumentsRepresentation(BsonSerializationContext context, TDictionary value) { var bsonWriter = context.Writer; bsonWriter.WriteStartArray(); foreach (DictionaryEntry dictionaryEntry in value) { bsonWriter.WriteStartDocument(); bsonWriter.WriteName("k"); _keySerializer.Serialize(context, dictionaryEntry.Key); bsonWriter.WriteName("v"); _valueSerializer.Serialize(context, dictionaryEntry.Value); bsonWriter.WriteEndDocument(); } bsonWriter.WriteEndArray(); } private void SerializeDocumentRepresentation(BsonSerializationContext context, TDictionary value) { var bsonWriter = context.Writer; bsonWriter.WriteStartDocument(); foreach (DictionaryEntry dictionaryEntry in value) { bsonWriter.WriteName(SerializeKeyString(dictionaryEntry.Key)); _valueSerializer.Serialize(context, dictionaryEntry.Value); } bsonWriter.WriteEndDocument(); } private string SerializeKeyString(object key) { var keyDocument = new BsonDocument(); using (var keyWriter = new BsonDocumentWriter(keyDocument)) { var context = BsonSerializationContext.CreateRoot(keyWriter); keyWriter.WriteStartDocument(); keyWriter.WriteName("k"); _keySerializer.Serialize(context, key); keyWriter.WriteEndDocument(); } var keyValue = keyDocument["k"]; if (keyValue.BsonType != BsonType.String) { throw new BsonSerializationException("When using DictionaryRepresentation.Document key values must serialize as strings."); } return (string)keyValue; } } /// /// Represents a serializer for dictionaries. /// /// The type of the dictionary. /// The type of the keys. /// The type of the values. public abstract class DictionarySerializerBase : ClassSerializerBase, IBsonArraySerializer, IBsonDocumentSerializer, IBsonDictionarySerializer where TDictionary : class, IEnumerable> { // private constants private static class Flags { public const long Key = 1; public const long Value = 2; } // private fields private readonly DictionaryRepresentation _dictionaryRepresentation; private readonly SerializerHelper _helper; private readonly Lazy> _lazyKeySerializer; private readonly Lazy> _lazyValueSerializer; // constructors /// /// Initializes a new instance of the class. /// public DictionarySerializerBase() : this(DictionaryRepresentation.Document) { } /// /// Initializes a new instance of the class. /// /// The dictionary representation. public DictionarySerializerBase(DictionaryRepresentation dictionaryRepresentation) : this(dictionaryRepresentation, BsonSerializer.SerializerRegistry) { } /// /// Initializes a new instance of the class. /// /// The dictionary representation. /// The key serializer. /// The value serializer. public DictionarySerializerBase(DictionaryRepresentation dictionaryRepresentation, IBsonSerializer keySerializer, IBsonSerializer valueSerializer) : this( dictionaryRepresentation, new Lazy>(() => keySerializer), new Lazy>(() => valueSerializer)) { if (keySerializer == null) { throw new ArgumentNullException("keySerializer"); } if (valueSerializer == null) { throw new ArgumentNullException("valueSerializer"); } } /// /// Initializes a new instance of the class. /// /// The dictionary representation. /// The serializer registry. public DictionarySerializerBase(DictionaryRepresentation dictionaryRepresentation, IBsonSerializerRegistry serializerRegistry) : this( dictionaryRepresentation, new Lazy>(() => serializerRegistry.GetSerializer()), new Lazy>(() => serializerRegistry.GetSerializer())) { if (serializerRegistry == null) { throw new ArgumentNullException("serializerRegistry"); } } private DictionarySerializerBase( DictionaryRepresentation dictionaryRepresentation, Lazy> lazyKeySerializer, Lazy> lazyValueSerializer) { _dictionaryRepresentation = dictionaryRepresentation; _lazyKeySerializer = lazyKeySerializer; _lazyValueSerializer = lazyValueSerializer; _helper = new SerializerHelper ( new SerializerHelper.Member("k", Flags.Key), new SerializerHelper.Member("v", Flags.Value) ); } // public properties /// /// Gets the dictionary representation. /// /// /// The dictionary representation. /// public DictionaryRepresentation DictionaryRepresentation { get { return _dictionaryRepresentation; } } /// /// Gets the key serializer. /// /// /// The key serializer. /// public IBsonSerializer KeySerializer { get { return _lazyKeySerializer.Value; } } /// /// Gets the value serializer. /// /// /// The value serializer. /// public IBsonSerializer ValueSerializer { get { return _lazyValueSerializer.Value; } } // public methods /// public bool TryGetItemSerializationInfo(out BsonSerializationInfo serializationInfo) { if (_dictionaryRepresentation != DictionaryRepresentation.ArrayOfDocuments) { serializationInfo = null; return false; } var serializer = new KeyValuePairSerializer( BsonType.Document, _lazyKeySerializer.Value, _lazyValueSerializer.Value); serializationInfo = new BsonSerializationInfo( null, serializer, serializer.ValueType); return true; } /// public bool TryGetMemberSerializationInfo(string memberName, out BsonSerializationInfo serializationInfo) { if (_dictionaryRepresentation != DictionaryRepresentation.Document) { serializationInfo = null; return false; } serializationInfo = new BsonSerializationInfo( memberName, _lazyValueSerializer.Value, _lazyValueSerializer.Value.ValueType); return true; } // protected methods /// /// Deserializes a value. /// /// The deserialization context. /// The deserialization args. /// A deserialized value. protected override TDictionary DeserializeValue(BsonDeserializationContext context, BsonDeserializationArgs args) { var bsonReader = context.Reader; var bsonType = bsonReader.GetCurrentBsonType(); switch (bsonType) { case BsonType.Array: return DeserializeArrayRepresentation(context); case BsonType.Document: return DeserializeDocumentRepresentation(context); default: throw CreateCannotDeserializeFromBsonTypeException(bsonType); } } /// /// Serializes a value. /// /// The serialization context. /// The serialization args. /// The object. protected override void SerializeValue(BsonSerializationContext context, BsonSerializationArgs args, TDictionary value) { var bsonWriter = context.Writer; switch (_dictionaryRepresentation) { case DictionaryRepresentation.Document: SerializeDocumentRepresentation(context, value); break; case DictionaryRepresentation.ArrayOfArrays: SerializeArrayOfArraysRepresentation(context, value); break; case DictionaryRepresentation.ArrayOfDocuments: SerializeArrayOfDocumentsRepresentation(context, value); break; default: var message = string.Format("'{0}' is not a valid IDictionary<{1}, {2}> representation.", _dictionaryRepresentation, BsonUtils.GetFriendlyTypeName(typeof(TKey)), BsonUtils.GetFriendlyTypeName(typeof(TValue))); throw new BsonSerializationException(message); } } // protected methods /// /// Creates an accumulator. /// /// The accumulator. protected virtual ICollection> CreateAccumulator() { #pragma warning disable 618 return (ICollection>)CreateInstance(); #pragma warning restore 618 } // protected methods /// /// Creates the instance. /// /// The instance. [Obsolete("CreateInstance is deprecated. Please use CreateAccumulator instead.")] protected virtual TDictionary CreateInstance() { throw new NotImplementedException(); } /// /// Finalizes an accumulator. /// /// The accumulator to finalize /// The instance. protected virtual TDictionary FinalizeAccumulator(ICollection> accumulator) { return (TDictionary)accumulator; } // private methods private TDictionary DeserializeArrayRepresentation(BsonDeserializationContext context) { var accumulator = CreateAccumulator(); var bsonReader = context.Reader; bsonReader.ReadStartArray(); while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) { TKey key; TValue value; var bsonType = bsonReader.GetCurrentBsonType(); switch (bsonType) { case BsonType.Array: bsonReader.ReadStartArray(); key = _lazyKeySerializer.Value.Deserialize(context); value = _lazyValueSerializer.Value.Deserialize(context); bsonReader.ReadEndArray(); break; case BsonType.Document: key = default(TKey); value = default(TValue); _helper.DeserializeMembers(context, (elementName, flag) => { switch (flag) { case Flags.Key: key = _lazyKeySerializer.Value.Deserialize(context); break; case Flags.Value: value = _lazyValueSerializer.Value.Deserialize(context); break; } }); break; default: throw CreateCannotDeserializeFromBsonTypeException(bsonType); } accumulator.Add(new KeyValuePair(key, value)); } bsonReader.ReadEndArray(); return FinalizeAccumulator(accumulator); } private TDictionary DeserializeDocumentRepresentation(BsonDeserializationContext context) { var accumulator = CreateAccumulator(); var bsonReader = context.Reader; bsonReader.ReadStartDocument(); while (bsonReader.ReadBsonType() != BsonType.EndOfDocument) { var key = DeserializeKeyString(bsonReader.ReadName()); var value = _lazyValueSerializer.Value.Deserialize(context); accumulator.Add(new KeyValuePair(key, value)); } bsonReader.ReadEndDocument(); return FinalizeAccumulator(accumulator); } private TKey DeserializeKeyString(string keyString) { var keyDocument = new BsonDocument("k", keyString); using (var keyReader = new BsonDocumentReader(keyDocument)) { var context = BsonDeserializationContext.CreateRoot(keyReader); keyReader.ReadStartDocument(); keyReader.ReadName("k"); var key = _lazyKeySerializer.Value.Deserialize(context); keyReader.ReadEndDocument(); return key; } } private void SerializeArrayOfArraysRepresentation(BsonSerializationContext context, TDictionary value) { var bsonWriter = context.Writer; bsonWriter.WriteStartArray(); foreach (var keyValuePair in value) { bsonWriter.WriteStartArray(); _lazyKeySerializer.Value.Serialize(context, keyValuePair.Key); _lazyValueSerializer.Value.Serialize(context, keyValuePair.Value); bsonWriter.WriteEndArray(); } bsonWriter.WriteEndArray(); } private void SerializeArrayOfDocumentsRepresentation(BsonSerializationContext context, TDictionary value) { var bsonWriter = context.Writer; bsonWriter.WriteStartArray(); foreach (var keyValuePair in value) { bsonWriter.WriteStartDocument(); bsonWriter.WriteName("k"); _lazyKeySerializer.Value.Serialize(context, keyValuePair.Key); bsonWriter.WriteName("v"); _lazyValueSerializer.Value.Serialize(context, keyValuePair.Value); bsonWriter.WriteEndDocument(); } bsonWriter.WriteEndArray(); } private void SerializeDocumentRepresentation(BsonSerializationContext context, TDictionary value) { var bsonWriter = context.Writer; bsonWriter.WriteStartDocument(); foreach (var keyValuePair in value) { bsonWriter.WriteName(SerializeKeyString(keyValuePair.Key)); _lazyValueSerializer.Value.Serialize(context, keyValuePair.Value); } bsonWriter.WriteEndDocument(); } private string SerializeKeyString(TKey key) { var keyDocument = new BsonDocument(); using (var keyWriter = new BsonDocumentWriter(keyDocument)) { var context = BsonSerializationContext.CreateRoot(keyWriter); keyWriter.WriteStartDocument(); keyWriter.WriteName("k"); _lazyKeySerializer.Value.Serialize(context, key); keyWriter.WriteEndDocument(); } var keyValue = keyDocument["k"]; if (keyValue.BsonType != BsonType.String) { throw new BsonSerializationException("When using DictionaryRepresentation.Document key values must serialize as strings."); } return (string)keyValue; } // explicit interface implementations IBsonSerializer IBsonDictionarySerializer.KeySerializer { get { return KeySerializer; } } IBsonSerializer IBsonDictionarySerializer.ValueSerializer { get { return ValueSerializer; } } } }