Strip Swan library Part 1
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace Swan.Mappers {
|
||||
/// <summary>
|
||||
/// Represents an attribute to select which properties are copyable between objects.
|
||||
/// </summary>
|
||||
/// <seealso cref="Attribute" />
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class CopyableAttribute : Attribute {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Swan.Mappers {
|
||||
/// <summary>
|
||||
/// Interface object map.
|
||||
/// </summary>
|
||||
public interface IObjectMap {
|
||||
/// <summary>
|
||||
/// Gets or sets the map.
|
||||
/// </summary>
|
||||
Dictionary<PropertyInfo, List<PropertyInfo>> Map {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the source.
|
||||
/// </summary>
|
||||
Type SourceType {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the destination.
|
||||
/// </summary>
|
||||
Type DestinationType {
|
||||
get;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Swan.Mappers {
|
||||
/// <summary>
|
||||
/// Represents an object map.
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">The type of the source.</typeparam>
|
||||
/// <typeparam name="TDestination">The type of the destination.</typeparam>
|
||||
/// <seealso cref="IObjectMap" />
|
||||
public class ObjectMap<TSource, TDestination> : IObjectMap {
|
||||
internal ObjectMap(IEnumerable<PropertyInfo> intersect) {
|
||||
this.SourceType = typeof(TSource);
|
||||
this.DestinationType = typeof(TDestination);
|
||||
this.Map = intersect.ToDictionary(property => this.DestinationType.GetProperty(property.Name), property => new List<PropertyInfo> { this.SourceType.GetProperty(property.Name) });
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Dictionary<PropertyInfo, List<PropertyInfo>> Map {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Type SourceType {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Type DestinationType {
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps the property.
|
||||
/// </summary>
|
||||
/// <typeparam name="TDestinationProperty">The type of the destination property.</typeparam>
|
||||
/// <typeparam name="TSourceProperty">The type of the source property.</typeparam>
|
||||
/// <param name="destinationProperty">The destination property.</param>
|
||||
/// <param name="sourceProperty">The source property.</param>
|
||||
/// <returns>
|
||||
/// An object map representation of type of the destination property
|
||||
/// and type of the source property.
|
||||
/// </returns>
|
||||
public ObjectMap<TSource, TDestination> MapProperty<TDestinationProperty, TSourceProperty>(Expression<Func<TDestination, TDestinationProperty>> destinationProperty, Expression<Func<TSource, TSourceProperty>> sourceProperty) {
|
||||
PropertyInfo propertyDestinationInfo = (destinationProperty.Body as MemberExpression)?.Member as PropertyInfo;
|
||||
|
||||
if(propertyDestinationInfo == null) {
|
||||
throw new ArgumentException("Invalid destination expression", nameof(destinationProperty));
|
||||
}
|
||||
|
||||
List<PropertyInfo> sourceMembers = GetSourceMembers(sourceProperty);
|
||||
|
||||
if(sourceMembers.Any() == false) {
|
||||
throw new ArgumentException("Invalid source expression", nameof(sourceProperty));
|
||||
}
|
||||
|
||||
// reverse order
|
||||
sourceMembers.Reverse();
|
||||
this.Map[propertyDestinationInfo] = sourceMembers;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the map property.
|
||||
/// </summary>
|
||||
/// <typeparam name="TDestinationProperty">The type of the destination property.</typeparam>
|
||||
/// <param name="destinationProperty">The destination property.</param>
|
||||
/// <returns>
|
||||
/// An object map representation of type of the destination property
|
||||
/// and type of the source property.
|
||||
/// </returns>
|
||||
/// <exception cref="System.Exception">Invalid destination expression.</exception>
|
||||
public ObjectMap<TSource, TDestination> RemoveMapProperty<TDestinationProperty>(Expression<Func<TDestination, TDestinationProperty>> destinationProperty) {
|
||||
PropertyInfo propertyDestinationInfo = (destinationProperty.Body as MemberExpression)?.Member as PropertyInfo;
|
||||
|
||||
if(propertyDestinationInfo == null) {
|
||||
throw new ArgumentException("Invalid destination expression", nameof(destinationProperty));
|
||||
}
|
||||
|
||||
if(this.Map.ContainsKey(propertyDestinationInfo)) {
|
||||
_ = this.Map.Remove(propertyDestinationInfo);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private static List<PropertyInfo> GetSourceMembers<TSourceProperty>(Expression<Func<TSource, TSourceProperty>> sourceProperty) {
|
||||
List<PropertyInfo> sourceMembers = new List<PropertyInfo>();
|
||||
MemberExpression initialExpression = sourceProperty.Body as MemberExpression;
|
||||
|
||||
while(true) {
|
||||
PropertyInfo propertySourceInfo = initialExpression?.Member as PropertyInfo;
|
||||
|
||||
if(propertySourceInfo == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
sourceMembers.Add(propertySourceInfo);
|
||||
initialExpression = initialExpression.Expression as MemberExpression;
|
||||
}
|
||||
|
||||
return sourceMembers;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.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>
|
||||
public partial class ObjectMapper {
|
||||
internal class PropertyInfoComparer : IEqualityComparer<PropertyInfo> {
|
||||
public Boolean Equals(PropertyInfo x, PropertyInfo y) => x != null && y != null && x.Name == y.Name && x.PropertyType == y.PropertyType;
|
||||
|
||||
public Int32 GetHashCode(PropertyInfo obj) => obj.Name.GetHashCode() + obj.PropertyType.Name.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
#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 {
|
||||
private static readonly Lazy<ObjectMapper> LazyInstance = new Lazy<ObjectMapper>(() => new ObjectMapper());
|
||||
|
||||
private readonly List<IObjectMap> _maps = new List<IObjectMap>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The current.
|
||||
/// </value>
|
||||
public static ObjectMapper Current => LazyInstance.Value;
|
||||
|
||||
/// <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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the map.
|
||||
/// </summary>
|
||||
/// <typeparam name="TSource">The type of the source.</typeparam>
|
||||
/// <typeparam name="TDestination">The type of the destination.</typeparam>
|
||||
/// <returns>
|
||||
/// An object map representation of type of the destination property
|
||||
/// and type of the source property.
|
||||
/// </returns>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// You can't create an existing map
|
||||
/// or
|
||||
/// Types doesn't match.
|
||||
/// </exception>
|
||||
public ObjectMap<TSource, TDestination> CreateMap<TSource, TDestination>() {
|
||||
if(this._maps.Any(x => x.SourceType == typeof(TSource) && x.DestinationType == typeof(TDestination))) {
|
||||
throw new InvalidOperationException("You can't create an existing map");
|
||||
}
|
||||
|
||||
IEnumerable<PropertyInfo> sourceType = PropertyTypeCache.DefaultCache.Value.RetrieveAllProperties<TSource>(true);
|
||||
IEnumerable<PropertyInfo> destinationType = PropertyTypeCache.DefaultCache.Value.RetrieveAllProperties<TDestination>(true);
|
||||
|
||||
PropertyInfo[] intersect = sourceType.Intersect(destinationType, new PropertyInfoComparer()).ToArray();
|
||||
|
||||
if(!intersect.Any()) {
|
||||
throw new InvalidOperationException("Types doesn't match");
|
||||
}
|
||||
|
||||
ObjectMap<TSource, TDestination> map = new ObjectMap<TSource, TDestination>(intersect);
|
||||
|
||||
this._maps.Add(map);
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps the specified source.
|
||||
/// </summary>
|
||||
/// <typeparam name="TDestination">The type of the destination.</typeparam>
|
||||
/// <param name="source">The source.</param>
|
||||
/// <param name="autoResolve">if set to <c>true</c> [automatic resolve].</param>
|
||||
/// <returns>
|
||||
/// A new instance of the map.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException">source.</exception>
|
||||
/// <exception cref="InvalidOperationException">You can't map from type {source.GetType().Name} to {typeof(TDestination).Name}.</exception>
|
||||
public TDestination Map<TDestination>(Object source, Boolean autoResolve = true) {
|
||||
if(source == null) {
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
}
|
||||
|
||||
TDestination destination = Activator.CreateInstance<TDestination>();
|
||||
IObjectMap map = this._maps.FirstOrDefault(x => x.SourceType == source.GetType() && x.DestinationType == typeof(TDestination));
|
||||
|
||||
if(map != null) {
|
||||
foreach(KeyValuePair<PropertyInfo, List<PropertyInfo>> property in map.Map) {
|
||||
Object finalSource = property.Value.Aggregate(source, (current, sourceProperty) => sourceProperty.GetValue(current)!);
|
||||
|
||||
property.Key.SetValue(destination, finalSource);
|
||||
}
|
||||
} else {
|
||||
if(!autoResolve) {
|
||||
throw new InvalidOperationException($"You can't map from type {source.GetType().Name} to {typeof(TDestination).Name}");
|
||||
}
|
||||
|
||||
// Missing mapping, try to use default behavior
|
||||
_ = Copy(source, destination);
|
||||
}
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
||||
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)))!;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user