/* 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 MongoDB.Bson.IO; using MongoDB.Bson.Serialization.Conventions; namespace MongoDB.Bson.Serialization.Serializers { /// /// Represents a serializer for objects. /// public class ObjectSerializer : ClassSerializerBase { // private static fields private static readonly ObjectSerializer __instance = new ObjectSerializer(); // private fields private readonly IDiscriminatorConvention _discriminatorConvention; // constructors /// /// Initializes a new instance of the class. /// public ObjectSerializer() : this(BsonSerializer.LookupDiscriminatorConvention(typeof(object))) { } /// /// Initializes a new instance of the class. /// /// The discriminator convention. /// discriminatorConvention public ObjectSerializer(IDiscriminatorConvention discriminatorConvention) { if (discriminatorConvention == null) { throw new ArgumentNullException("discriminatorConvention"); } _discriminatorConvention = discriminatorConvention; } // public static properties /// /// Gets the standard instance. /// /// /// The standard instance. /// public static ObjectSerializer Instance { get { return __instance; } } // public methods /// /// Deserializes a value. /// /// The deserialization context. /// The deserialization args. /// A deserialized value. public override object Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) { var bsonReader = context.Reader; var bsonType = bsonReader.GetCurrentBsonType(); switch (bsonType) { case BsonType.Array: if (context.DynamicArraySerializer != null) { return context.DynamicArraySerializer.Deserialize(context); } goto default; case BsonType.Binary: var binaryData = bsonReader.ReadBinaryData(); var subType = binaryData.SubType; if (subType == BsonBinarySubType.UuidStandard || subType == BsonBinarySubType.UuidLegacy) { return binaryData.ToGuid(); } goto default; case BsonType.Boolean: return bsonReader.ReadBoolean(); case BsonType.DateTime: var millisecondsSinceEpoch = bsonReader.ReadDateTime(); var bsonDateTime = new BsonDateTime(millisecondsSinceEpoch); return bsonDateTime.ToUniversalTime(); case BsonType.Decimal128: return bsonReader.ReadDecimal128(); case BsonType.Document: return DeserializeDiscriminatedValue(context, args); case BsonType.Double: return bsonReader.ReadDouble(); case BsonType.Int32: return bsonReader.ReadInt32(); case BsonType.Int64: return bsonReader.ReadInt64(); case BsonType.Null: bsonReader.ReadNull(); return null; case BsonType.ObjectId: return bsonReader.ReadObjectId(); case BsonType.String: return bsonReader.ReadString(); default: var message = string.Format("ObjectSerializer does not support BSON type '{0}'.", bsonType); throw new FormatException(message); } } /// /// Serializes a value. /// /// The serialization context. /// The serialization args. /// The object. public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value) { var bsonWriter = context.Writer; if (value == null) { bsonWriter.WriteNull(); } else { var actualType = value.GetType(); if (actualType == typeof(object)) { bsonWriter.WriteStartDocument(); bsonWriter.WriteEndDocument(); } else { // certain types can be written directly as BSON value // if we're not at the top level document, or if we're using the JsonWriter if (bsonWriter.State == BsonWriterState.Value || bsonWriter is JsonWriter) { switch (Type.GetTypeCode(actualType)) { case TypeCode.Boolean: bsonWriter.WriteBoolean((bool)value); return; case TypeCode.DateTime: // TODO: is this right? will lose precision after round trip var bsonDateTime = new BsonDateTime(BsonUtils.ToUniversalTime((DateTime)value)); bsonWriter.WriteDateTime(bsonDateTime.MillisecondsSinceEpoch); return; case TypeCode.Double: bsonWriter.WriteDouble((double)value); return; case TypeCode.Int16: // TODO: is this right? will change type to Int32 after round trip bsonWriter.WriteInt32((short)value); return; case TypeCode.Int32: bsonWriter.WriteInt32((int)value); return; case TypeCode.Int64: bsonWriter.WriteInt64((long)value); return; case TypeCode.Object: if (actualType == typeof(Decimal128)) { var decimal128 = (Decimal128)value; bsonWriter.WriteDecimal128(decimal128); return; } if (actualType == typeof(Guid)) { var guid = (Guid)value; var guidRepresentation = bsonWriter.Settings.GuidRepresentation; var binaryData = new BsonBinaryData(guid, guidRepresentation); bsonWriter.WriteBinaryData(binaryData); return; } if (actualType == typeof(ObjectId)) { bsonWriter.WriteObjectId((ObjectId)value); return; } break; case TypeCode.String: bsonWriter.WriteString((string)value); return; } } SerializeDiscriminatedValue(context, args, value, actualType); } } } // private methods private object DeserializeDiscriminatedValue(BsonDeserializationContext context, BsonDeserializationArgs args) { var bsonReader = context.Reader; var actualType = _discriminatorConvention.GetActualType(bsonReader, typeof(object)); if (actualType == typeof(object)) { var type = bsonReader.GetCurrentBsonType(); switch(type) { case BsonType.Document: if (context.DynamicDocumentSerializer != null) { return context.DynamicDocumentSerializer.Deserialize(context, args); } break; } bsonReader.ReadStartDocument(); bsonReader.ReadEndDocument(); return new object(); } else { var serializer = BsonSerializer.LookupSerializer(actualType); var polymorphicSerializer = serializer as IBsonPolymorphicSerializer; if (polymorphicSerializer != null && polymorphicSerializer.IsDiscriminatorCompatibleWithObjectSerializer) { return serializer.Deserialize(context, args); } else { object value = null; var wasValuePresent = false; bsonReader.ReadStartDocument(); while (bsonReader.ReadBsonType() != 0) { var name = bsonReader.ReadName(); if (name == _discriminatorConvention.ElementName) { bsonReader.SkipValue(); } else if (name == "_v") { value = serializer.Deserialize(context); wasValuePresent = true; } else { var message = string.Format("Unexpected element name: '{0}'.", name); throw new FormatException(message); } } bsonReader.ReadEndDocument(); if (!wasValuePresent) { throw new FormatException("_v element missing."); } return value; } } } private void SerializeDiscriminatedValue(BsonSerializationContext context, BsonSerializationArgs args, object value, Type actualType) { var serializer = BsonSerializer.LookupSerializer(actualType); var polymorphicSerializer = serializer as IBsonPolymorphicSerializer; if (polymorphicSerializer != null && polymorphicSerializer.IsDiscriminatorCompatibleWithObjectSerializer) { serializer.Serialize(context, args, value); } else { if (context.IsDynamicType != null && context.IsDynamicType(value.GetType())) { args.NominalType = actualType; serializer.Serialize(context, args, value); } else { var bsonWriter = context.Writer; var discriminator = _discriminatorConvention.GetDiscriminator(typeof(object), actualType); bsonWriter.WriteStartDocument(); bsonWriter.WriteName(_discriminatorConvention.ElementName); BsonValueSerializer.Instance.Serialize(context, discriminator); bsonWriter.WriteName("_v"); serializer.Serialize(context, value); bsonWriter.WriteEndDocument(); } } } } }