/* Copyright 2016-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.Generic; using System.ComponentModel; using System.Reflection; using MongoDB.Bson; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Serializers; using MongoDB.Driver.Support; namespace MongoDB.Driver { internal static class FieldValueSerializerHelper { public static IBsonSerializer GetSerializerForValueType(IBsonSerializer fieldSerializer, IBsonSerializerRegistry serializerRegistry, Type valueType) { return GetSerializerForValueType(fieldSerializer, serializerRegistry, valueType, 0); } private static IBsonSerializer GetSerializerForValueType(IBsonSerializer fieldSerializer, IBsonSerializerRegistry serializerRegistry, Type valueType, int recursionLevel) { if (recursionLevel > 1) { throw new ArgumentException("Unexpectedly high recursion level.", nameof(recursionLevel)); } var fieldType = fieldSerializer.ValueType; // these will normally be equal unless we've removed some Convert(s) that the compiler put in if (fieldType == valueType) { return fieldSerializer; } // serialize numeric values without converting them if (fieldType.IsNumeric() && valueType.IsNumeric()) { var valueSerializer = BsonSerializer.SerializerRegistry.GetSerializer(valueType); if (HasStringRepresentation(fieldSerializer)) { valueSerializer = WithStringRepresentation(valueSerializer); } return valueSerializer; } var fieldTypeInfo = fieldType.GetTypeInfo(); var fieldSerializerInterfaceType = typeof(IBsonSerializer<>).MakeGenericType(fieldType); var valueTypeInfo = valueType.GetTypeInfo(); // synthesize a NullableSerializer using the field serializer if (valueType.IsNullable() && valueType.GetNullableUnderlyingType() == fieldType) { var nullableSerializerType = typeof(NullableSerializer<>).MakeGenericType(fieldType); var nullableSerializerConstructor = nullableSerializerType.GetTypeInfo().GetConstructor(new[] { fieldSerializerInterfaceType }); return (IBsonSerializer)nullableSerializerConstructor.Invoke(new object[] { fieldSerializer }); } // synthesize an EnumConvertingSerializer using the field serializer if (fieldTypeInfo.IsEnum) { if (valueType.IsConvertibleToEnum()) { var enumConvertingSerializerType = typeof(EnumConvertingSerializer<,>).MakeGenericType(valueType, fieldType); var enumConvertingSerializerConstructor = enumConvertingSerializerType.GetTypeInfo().GetConstructor(new[] { fieldSerializerInterfaceType }); return (IBsonSerializer)enumConvertingSerializerConstructor.Invoke(new object[] { fieldSerializer }); } if (valueType.IsNullable() && valueType.GetNullableUnderlyingType().IsConvertibleToEnum()) { var underlyingValueType = valueType.GetNullableUnderlyingType(); var underlyingValueSerializerInterfaceType = typeof(IBsonSerializer<>).MakeGenericType(underlyingValueType); var enumConvertingSerializerType = typeof(EnumConvertingSerializer<,>).MakeGenericType(underlyingValueType, fieldType); var enumConvertingSerializerConstructor = enumConvertingSerializerType.GetTypeInfo().GetConstructor(new[] { fieldSerializerInterfaceType }); var enumConvertingSerializer = enumConvertingSerializerConstructor.Invoke(new object[] { fieldSerializer }); var nullableSerializerType = typeof(NullableSerializer<>).MakeGenericType(underlyingValueType); var nullableSerializerConstructor = nullableSerializerType.GetTypeInfo().GetConstructor(new[] { underlyingValueSerializerInterfaceType }); return (IBsonSerializer)nullableSerializerConstructor.Invoke(new object[] { enumConvertingSerializer }); } } // synthesize a NullableEnumConvertingSerializer using the field serializer if (fieldType.IsNullableEnum() && valueType.IsNullable()) { var nonNullableFieldType = fieldType.GetNullableUnderlyingType(); var nonNullableValueType = valueType.GetNullableUnderlyingType(); var nonNullableFieldSerializer = ((IChildSerializerConfigurable)fieldSerializer).ChildSerializer; var nonNullableFieldSerializerInterfaceType = typeof(IBsonSerializer<>).MakeGenericType(nonNullableFieldType); var nullableEnumConvertingSerializerType = typeof(NullableEnumConvertingSerializer<,>).MakeGenericType(nonNullableValueType, nonNullableFieldType); var nullableEnumConvertingSerializerConstructor = nullableEnumConvertingSerializerType.GetTypeInfo().GetConstructor(new[] { nonNullableFieldSerializerInterfaceType }); return (IBsonSerializer)nullableEnumConvertingSerializerConstructor.Invoke(new object[] { nonNullableFieldSerializer }); } // synthesize an IEnumerableSerializer serializer using the item serializer from the field serializer Type fieldIEnumerableInterfaceType; Type valueIEnumerableInterfaceType; Type itemType; if ( (fieldIEnumerableInterfaceType = fieldType.FindIEnumerable()) != null && (valueIEnumerableInterfaceType = valueType.FindIEnumerable()) != null && (itemType = fieldIEnumerableInterfaceType.GetSequenceElementType()) == valueIEnumerableInterfaceType.GetSequenceElementType() && fieldSerializer is IChildSerializerConfigurable) { var itemSerializer = ((IChildSerializerConfigurable)fieldSerializer).ChildSerializer; var itemSerializerInterfaceType = typeof(IBsonSerializer<>).MakeGenericType(itemType); var ienumerableSerializerType = typeof(IEnumerableSerializer<>).MakeGenericType(itemType); var ienumerableSerializerConstructor = ienumerableSerializerType.GetTypeInfo().GetConstructor(new[] { itemSerializerInterfaceType }); return (IBsonSerializer)ienumerableSerializerConstructor.Invoke(new object[] { itemSerializer }); } // if the fieldSerializer is an array serializer try to adapt its itemSerializer for valueType IBsonArraySerializer arraySerializer; if ((arraySerializer = fieldSerializer as IBsonArraySerializer) != null) { BsonSerializationInfo itemSerializationInfo; if (arraySerializer.TryGetItemSerializationInfo(out itemSerializationInfo)) { if (recursionLevel == 0) { var itemSerializer = itemSerializationInfo.Serializer; return GetSerializerForValueType(itemSerializer, serializerRegistry, valueType, recursionLevel + 1); } } } // if we can't return a value serializer based on the field serializer return a converting serializer var convertIfPossibleSerializerType = typeof(ConvertIfPossibleSerializer<,>).MakeGenericType(valueType, fieldType); var convertIfPossibleSerializerConstructor = convertIfPossibleSerializerType.GetTypeInfo().GetConstructor(new[] { fieldSerializerInterfaceType, typeof(IBsonSerializerRegistry) }); return (IBsonSerializer)convertIfPossibleSerializerConstructor.Invoke(new object[] { fieldSerializer, serializerRegistry }); } public static IBsonSerializer GetSerializerForValueType(IBsonSerializer fieldSerializer, IBsonSerializerRegistry serializerRegistry, Type valueType, object value) { if (!valueType.GetTypeInfo().IsValueType && value == null) { return fieldSerializer; } else { return GetSerializerForValueType(fieldSerializer, serializerRegistry, valueType); } } // private static methods private static bool HasStringRepresentation(IBsonSerializer serializer) { var configurableSerializer = serializer as IRepresentationConfigurable; if (configurableSerializer != null) { return configurableSerializer.Representation == BsonType.String; } else { return false; } } private static IBsonSerializer WithStringRepresentation(IBsonSerializer serializer) { var configurableSerializer = serializer as IRepresentationConfigurable; if (configurableSerializer != null) { return configurableSerializer.WithRepresentation(BsonType.String); } else { return serializer; } } // nested types private class ConvertIfPossibleSerializer : SerializerBase { private readonly IBsonSerializer _serializer; private readonly IBsonSerializerRegistry _serializerRegistry; public ConvertIfPossibleSerializer(IBsonSerializer serializer, IBsonSerializerRegistry serializerRegistry) { _serializer = serializer; _serializerRegistry = serializerRegistry; } public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, TFrom value) { TTo convertedValue; if (TryConvertValue(value, out convertedValue)) { args.NominalType = typeof(TTo); _serializer.Serialize(context, args, convertedValue); } else { var serializer = _serializerRegistry.GetSerializer(); serializer.Serialize(context, args, value); } } private bool TryConvertValue(TFrom value, out TTo convertedValue) { if (object.ReferenceEquals(value, null)) { convertedValue = (TTo)(object)null; return true; } Type fromType = value.GetType(); Type toType = typeof(TTo); if (toType.GetTypeInfo().IsAssignableFrom(fromType)) { convertedValue = (TTo)(object)value; return true; } var toConverter = TypeDescriptor.GetConverter(toType); if (toConverter.CanConvertFrom(fromType)) { convertedValue = (TTo)toConverter.ConvertFrom(value); return true; } var fromConverter = TypeDescriptor.GetConverter(fromType); if (fromConverter.CanConvertTo(toType)) { convertedValue = (TTo)fromConverter.ConvertTo(value, toType); return true; } try { convertedValue = (TTo)Convert.ChangeType(value, toType); return true; } catch { } convertedValue = default(TTo); return false; } } private class EnumConvertingSerializer : SerializerBase { private readonly IBsonSerializer _serializer; public EnumConvertingSerializer(IBsonSerializer serializer) { _serializer = serializer; } public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, TFrom value) { TTo convertedValue; if (typeof(TFrom) == typeof(string)) { convertedValue = (TTo)Enum.Parse(typeof(TTo), (string)(object)value); } else { convertedValue = (TTo)Enum.ToObject(typeof(TTo), (object)value); } _serializer.Serialize(context, args, convertedValue); } } private class IEnumerableSerializer : SerializerBase> { private readonly IBsonSerializer _itemSerializer; public IEnumerableSerializer(IBsonSerializer itemSerializer) { _itemSerializer = itemSerializer; } public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, IEnumerable value) { var bsonWriter = context.Writer; if (value == null) { bsonWriter.WriteNull(); } else { bsonWriter.WriteStartArray(); foreach (var item in value) { _itemSerializer.Serialize(context, item); } bsonWriter.WriteEndArray(); } } } private class NullableEnumConvertingSerializer : SerializerBase> where TFrom : struct where TTo : struct { private readonly IBsonSerializer _nonNullableEnumSerializer; public NullableEnumConvertingSerializer(IBsonSerializer nonNullableEnumSerializer) { _nonNullableEnumSerializer = nonNullableEnumSerializer; } public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, Nullable value) { if (value == null) { context.Writer.WriteNull(); } else { _nonNullableEnumSerializer.Serialize(context, args, (TTo)Enum.ToObject(typeof(TTo), (object)value.Value)); } } } } }