#nullable enable using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; using Swan.Reflection; namespace Swan.Mappers { /// /// Represents an AutoMapper-like object to map from one object type /// to another using defined properties map or using the default behaviour /// to copy same named properties from one object to another. /// /// The extension methods like CopyPropertiesTo use the default behaviour. /// /// /// The following code explains how to map an object's properties into an instance of type T. /// /// using Swan.Mappers; /// /// class Example /// { /// class Person /// { /// public string Name { get; set; } /// public int Age { get; set; } /// } /// /// static void Main() /// { /// var obj = new { Name = "John", Age = 42 }; /// /// var person = Runtime.ObjectMapper.Map<Person>(obj); /// } /// } /// /// /// The following code explains how to explicitly map certain properties. /// /// using Swan.Mappers; /// /// class Example /// { /// class User /// { /// public string Name { get; set; } /// public Role Role { get; set; } /// } /// /// public class Role /// { /// public string Name { get; set; } /// } /// /// class UserDto /// { /// public string Name { get; set; } /// public string Role { get; set; } /// } /// /// static void Main() /// { /// // create a User object /// var person = /// new User { Name = "Phillip", Role = new Role { Name = "Admin" } }; /// /// // create an Object Mapper /// var mapper = new ObjectMapper(); /// /// // map the User's Role.Name to UserDto's Role /// mapper.CreateMap<User, UserDto>() /// .MapProperty(d => d.Role, x => x.Role.Name); /// /// // apply the previous map and retrieve a UserDto object /// var destination = mapper.Map<UserDto>(person); /// } /// } /// /// public partial class ObjectMapper { /// /// Copies the specified source. /// /// The source. /// The target. /// The properties to copy. /// The ignore properties. /// /// Copied properties count. /// /// /// source /// or /// target. /// public static Int32 Copy(Object source, Object? target, IEnumerable? propertiesToCopy = null, params String[]? ignoreProperties) { if(source == null) { throw new ArgumentNullException(nameof(source)); } if(target == null) { throw new ArgumentNullException(nameof(target)); } return CopyInternal(target, GetSourceMap(source), propertiesToCopy, ignoreProperties); } /// /// Copies the specified source. /// /// The source. /// The target. /// The properties to copy. /// The ignore properties. /// /// Copied properties count. /// /// /// source /// or /// target. /// public static Int32 Copy(IDictionary source, Object? target, IEnumerable? propertiesToCopy = null, params String[] ignoreProperties) { if(source == null) { throw new ArgumentNullException(nameof(source)); } if(target == null) { throw new ArgumentNullException(nameof(target)); } return CopyInternal(target, source.ToDictionary(x => x.Key.ToLowerInvariant(), x => Tuple.Create(typeof(Object), x.Value)), propertiesToCopy, ignoreProperties); } private static Int32 CopyInternal(Object target, Dictionary> sourceProperties, IEnumerable? propertiesToCopy, IEnumerable? ignoreProperties) { // Filter properties IEnumerable? requiredProperties = propertiesToCopy?.Where(p => !String.IsNullOrWhiteSpace(p)).Select(p => p.ToLowerInvariant()); IEnumerable? ignoredProperties = ignoreProperties?.Where(p => !String.IsNullOrWhiteSpace(p)).Select(p => p.ToLowerInvariant()); IEnumerable properties = PropertyTypeCache.DefaultCache.Value.RetrieveFilteredProperties(target.GetType(), true, x => x.CanWrite); return properties.Select(x => x.Name).Distinct().ToDictionary(x => x.ToLowerInvariant(), x => properties.First(y => y.Name == x)).Where(x => sourceProperties.Keys.Contains(x.Key)).When(() => requiredProperties != null, q => q.Where(y => requiredProperties.Contains(y.Key))).When(() => ignoredProperties != null, q => q.Where(y => !ignoredProperties.Contains(y.Key))).ToDictionary(x => x.Value, x => sourceProperties[x.Key]).Sum(x => TrySetValue(x.Key, x.Value, target) ? 1 : 0); } private static Boolean TrySetValue(PropertyInfo propertyInfo, Tuple property, Object target) { try { (Type type, Object value) = property; if(type.IsEnum) { propertyInfo.SetValue(target, Enum.ToObject(propertyInfo.PropertyType, value)); return true; } if(type.IsValueType || propertyInfo.PropertyType != type) { return propertyInfo.TrySetBasicType(value, target); } if(propertyInfo.PropertyType.IsArray) { _ = propertyInfo.TrySetArray(value as IEnumerable, target); return true; } propertyInfo.SetValue(target, GetValue(value, propertyInfo.PropertyType)); return true; } catch { // swallow } return false; } private static Object? GetValue(Object source, Type targetType) { if(source == null) { return null; } Object? target = null; source.CreateTarget(targetType, false, ref target); switch(source) { case String _: target = source; break; case IList sourceList when target is IList targetList: MethodInfo addMethod = targetType.GetMethods().FirstOrDefault(m => m.Name == Formatters.Json.AddMethodName && m.IsPublic && m.GetParameters().Length == 1); if(addMethod == null) { return target; } Boolean? isItemValueType = targetList.GetType().GetElementType()?.IsValueType; foreach(Object? item in sourceList) { try { if(isItemValueType != null) { _ = targetList.Add((Boolean)isItemValueType ? item : item?.CopyPropertiesToNew()); } } catch { // ignored } } break; default: _ = source.CopyPropertiesTo(target); break; } return target; } private static Dictionary> GetSourceMap(Object source) { // select distinct properties because they can be duplicated by inheritance PropertyInfo[] sourceProperties = PropertyTypeCache.DefaultCache.Value.RetrieveFilteredProperties(source.GetType(), true, x => x.CanRead).ToArray(); return sourceProperties.Select(x => x.Name).Distinct().ToDictionary(x => x.ToLowerInvariant(), x => Tuple.Create(sourceProperties.First(y => y.Name == x).PropertyType, sourceProperties.First(y => y.Name == x).GetValue(source)))!; } } }