/* 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; }
}
}
}