226 lines
7.9 KiB
C#
226 lines
7.9 KiB
C#
#nullable enable
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using Swan.Reflection;
|
|
|
|
namespace Swan.Mappers {
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <example>
|
|
/// The following code explains how to map an object's properties into an instance of type T.
|
|
/// <code>
|
|
/// 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);
|
|
/// }
|
|
/// }
|
|
/// </code>
|
|
///
|
|
/// The following code explains how to explicitly map certain properties.
|
|
/// <code>
|
|
/// 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);
|
|
/// }
|
|
/// }
|
|
/// </code>
|
|
/// </example>
|
|
public partial class ObjectMapper {
|
|
/// <summary>
|
|
/// Copies the specified source.
|
|
/// </summary>
|
|
/// <param name="source">The source.</param>
|
|
/// <param name="target">The target.</param>
|
|
/// <param name="propertiesToCopy">The properties to copy.</param>
|
|
/// <param name="ignoreProperties">The ignore properties.</param>
|
|
/// <returns>
|
|
/// Copied properties count.
|
|
/// </returns>
|
|
/// <exception cref="ArgumentNullException">
|
|
/// source
|
|
/// or
|
|
/// target.
|
|
/// </exception>
|
|
public static Int32 Copy(Object source, Object? target, IEnumerable<String>? 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copies the specified source.
|
|
/// </summary>
|
|
/// <param name="source">The source.</param>
|
|
/// <param name="target">The target.</param>
|
|
/// <param name="propertiesToCopy">The properties to copy.</param>
|
|
/// <param name="ignoreProperties">The ignore properties.</param>
|
|
/// <returns>
|
|
/// Copied properties count.
|
|
/// </returns>
|
|
/// <exception cref="ArgumentNullException">
|
|
/// source
|
|
/// or
|
|
/// target.
|
|
/// </exception>
|
|
public static Int32 Copy(IDictionary<String, Object> source, Object? target, IEnumerable<String>? 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<String, Tuple<Type, Object>> sourceProperties, IEnumerable<String>? propertiesToCopy, IEnumerable<String>? ignoreProperties) {
|
|
// Filter properties
|
|
IEnumerable<String>? requiredProperties = propertiesToCopy?.Where(p => !String.IsNullOrWhiteSpace(p)).Select(p => p.ToLowerInvariant());
|
|
|
|
IEnumerable<String>? ignoredProperties = ignoreProperties?.Where(p => !String.IsNullOrWhiteSpace(p)).Select(p => p.ToLowerInvariant());
|
|
|
|
IEnumerable<PropertyInfo> 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<Type, Object> 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<Object>, 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<Object>());
|
|
}
|
|
} catch {
|
|
// ignored
|
|
}
|
|
}
|
|
|
|
break;
|
|
default:
|
|
_ = source.CopyPropertiesTo(target);
|
|
break;
|
|
}
|
|
|
|
return target;
|
|
}
|
|
|
|
private static Dictionary<String, Tuple<Type, Object>> 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)))!;
|
|
}
|
|
}
|
|
}
|