Skip to content

Commit

Permalink
Add CopyOnlyPropertiesTo method and add CopyHelper (#25)
Browse files Browse the repository at this point in the history
* Add CopyOnlyPropertiesTo method and add CopyHelper

* Create ObjectCopier

* Minor change to allow identify object type

* Fix test
  • Loading branch information
chubbsnes65 authored and geoperez committed Aug 15, 2017
1 parent 1a7e358 commit 3ac460d
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 67 deletions.
104 changes: 104 additions & 0 deletions src/Unosquare.Swan/Components/ObjectCopier.cs
@@ -0,0 +1,104 @@
namespace Unosquare.Swan.Components
{
using System;
using System.Linq;
using System.Reflection;
using Unosquare.Swan.Reflection;

/// <summary>
/// Copy from one object to other one
/// </summary>
public static class ObjectCopier
{
private static readonly Lazy<PropertyTypeCache> CopyPropertiesTargets = new Lazy<PropertyTypeCache>(() => new PropertyTypeCache());
private static readonly Lazy<PropertyTypeCache> CopyPropertiesSources = new Lazy<PropertyTypeCache>(() => new PropertyTypeCache());

/// <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>
public static int Copy(
object source,
object target,
string[] propertiesToCopy = null,
string[] ignoreProperties = null)
{
var copiedProperties = 0;

// Sources
var sourceType = source.GetType();

var sourceProperties = CopyPropertiesSources.Value.Retrieve(sourceType, () =>
{
return sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(x => x.CanRead);
});

// Targets
var targetType = target.GetType();
var targetProperties = CopyPropertiesTargets.Value.Retrieve(targetType, () =>
{
return targetType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(x => x.CanWrite);
});

// Filter properties
var targetPropertyNames = targetProperties.Select(t => t.Name.ToLowerInvariant());
var filteredSourceProperties = sourceProperties
.Where(s => targetPropertyNames.Contains(s.Name.ToLowerInvariant()))
.ToArray();

var requiredProperties = propertiesToCopy?.Where(p => string.IsNullOrWhiteSpace(p) == false)
.Select(p => p.ToLowerInvariant())
.ToArray() ?? null;

var ignoredProperties = ignoreProperties?.Where(p => string.IsNullOrWhiteSpace(p) == false)
.Select(p => p.ToLowerInvariant())
.ToArray() ?? null;

// Copy source properties
foreach (var sourceProperty in filteredSourceProperties)
{

var targetProperty = targetProperties.SingleOrDefault(s => s.Name.ToLowerInvariant() == sourceProperty.Name.ToLowerInvariant());
if (targetProperty == null) continue;

if (requiredProperties != null && requiredProperties.Contains(targetProperty.Name.ToLowerInvariant()) == false)
continue;

if (ignoredProperties != null && ignoredProperties.Contains(targetProperty.Name.ToLowerInvariant()))
continue;

try
{
// Direct Copy
if (targetProperty.PropertyType == sourceProperty.PropertyType)
{
targetProperty.SetValue(target, sourceProperty.GetValue(source));
copiedProperties++;
continue;
}

// String to target type conversion
var sourceStringValue = sourceProperty.GetValue(source).ToStringInvariant();
object targetValue;
if (Definitions.BasicTypesInfo[targetProperty.PropertyType].TryParse(sourceStringValue, out targetValue))
{
targetProperty.SetValue(target, targetValue);
copiedProperties++;
}
}
catch
{
// swallow
}
}

return copiedProperties;
}
}
}
107 changes: 42 additions & 65 deletions src/Unosquare.Swan/Extensions.cs
Expand Up @@ -14,7 +14,6 @@
public static partial class Extensions
{
private static readonly Lazy<PropertyTypeCache> CopyPropertiesTargets = new Lazy<PropertyTypeCache>(() => new PropertyTypeCache());
private static readonly Lazy<PropertyTypeCache> CopyPropertiesSources = new Lazy<PropertyTypeCache>(() => new PropertyTypeCache());

/// <summary>
/// Iterates over the public, instance, readable properties of the source and
Expand All @@ -41,77 +40,41 @@ public static int CopyPropertiesTo<T>(this T source, object target)
/// <returns>Returns the number of properties that were successfully copied</returns>
public static int CopyPropertiesTo(this object source, object target, string[] ignoreProperties)
{
// TODO: Add recursive so child objects can be copied also
var copiedProperties = 0;

// Sources
var sourceType = source.GetType();
var sourceProperties = CopyPropertiesSources.Value.Retrieve(sourceType, () =>
{
return sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(x => x.CanRead && Definitions.AllBasicTypes.Contains(x.PropertyType));
});

// Targets
var targetType = target.GetType();
var targetProperties = CopyPropertiesTargets.Value.Retrieve(targetType, () =>
{
return targetType.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(x => x.CanWrite && Definitions.AllBasicTypes.Contains(x.PropertyType));
});

// Filter properties
var targetPropertyNames = targetProperties.Select(t => t.Name.ToLowerInvariant());
var filteredSourceProperties = sourceProperties
.Where(s => targetPropertyNames.Contains(s.Name.ToLowerInvariant()))
.ToArray();

var ignoredProperties = ignoreProperties?.Where(p => string.IsNullOrWhiteSpace(p) == false)
.Select(p => p.ToLowerInvariant())
.ToArray() ?? new string[] { };

// Copy source properties
foreach (var sourceProperty in filteredSourceProperties)
{
var targetProperty = targetProperties.SingleOrDefault(s => s.Name.ToLowerInvariant() == sourceProperty.Name.ToLowerInvariant());
if (targetProperty == null) continue;

// Skip over ignored properties
if (ignoredProperties.Contains(targetProperty.Name.ToLowerInvariant()))
continue;

try
{
// Direct Copy
if (targetProperty.PropertyType == sourceProperty.PropertyType)
{
targetProperty.SetValue(target, sourceProperty.GetValue(source));
copiedProperties++;
continue;
}
return Components.ObjectCopier.Copy(source, target, null, ignoreProperties);
}

// String to target type conversion
var sourceStringValue = sourceProperty.GetValue(source).ToStringInvariant();
object targetValue;
if (Definitions.BasicTypesInfo[targetProperty.PropertyType].TryParse(sourceStringValue, out targetValue))
{
targetProperty.SetValue(target, targetValue);
copiedProperties++;
}
}
catch
{
// swallow
}
}
/// <summary>
/// Iterates over the public, instance, readable properties of the source and
/// tries to write a compatible value to a public, instance, writable property in the destination
/// This method only supports basic types and it is not multi level
/// </summary>
/// <typeparam name="T">The type of the source.</typeparam>
/// <param name="source">The source.</param>
/// <param name="target">The target.</param>
/// <returns>Number of properties that was copied successful</returns>
public static int CopyOnlyPropertiesTo<T>(this T source, object target)
{
return CopyOnlyPropertiesTo(source, target, null);
}

return copiedProperties;
/// <summary>
/// Iterates over the public, instance, readable properties of the source and
/// tries to write a compatible value to a public, instance, writable property in the destination
/// This method only supports basic types and it is not multi level
/// </summary>
/// <param name="source">The source.</param>
/// <param name="target">The destination.</param>
/// <param name="propertiesToCopy">Properties to copy.</param>
/// <returns>Returns the number of properties that were successfully copied</returns>
public static int CopyOnlyPropertiesTo(this object source, object target, string[] propertiesToCopy)
{
return Components.ObjectCopier.Copy(source, target, propertiesToCopy, null);
}

/// <summary>
/// Copies the properties to new.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="T">Object Type</typeparam>
/// <param name="source">The source.</param>
/// <param name="ignoreProperties">The ignore properties.</param>
/// <returns>Returns the specified type of properties that were successfully copied</returns>
Expand All @@ -122,6 +85,20 @@ public static T CopyPropertiesToNew<T>(this object source, string[] ignoreProper
return target;
}

/// <summary>
/// Copies the only properties to new.
/// </summary>
/// <typeparam name="T">Object Type</typeparam>
/// <param name="source">The source.</param>
/// <param name="propertiesToCopy">The properties to copy.</param>
/// <returns>Returns the specified type of properties that were successfully copied</returns>
public static T CopyOnlyPropertiesToNew<T>(this object source, string[] propertiesToCopy = null)
{
var target = Activator.CreateInstance<T>();
source.CopyOnlyPropertiesTo(target, propertiesToCopy);
return target;
}

/// <summary>
/// Iterates over the keys of the source and tries to write a compatible value to a public,
/// instance, writable property in the destination.
Expand Down
29 changes: 28 additions & 1 deletion test/Unosquare.Swan.Test/ExtensionsTest.cs
Expand Up @@ -35,6 +35,19 @@ public void IgnoredPropertiesTest()
Assert.AreEqual(source.StringData, destination.StringData);
}

[Test]
public void OnlyPropertiesTest()
{
var source = BasicJson.GetDefault();
var destination = new BasicJson();
string[] Only = { "NegativeInt", "BoolData" };
source.CopyOnlyPropertiesTo(destination, Only);

Assert.AreEqual(source.BoolData, destination.BoolData);
Assert.AreEqual(source.NegativeInt, destination.NegativeInt);
Assert.AreNotEqual(source.StringData, destination.StringData);
}

[Test]
public void CopyPropertiesToNewTest()
{
Expand All @@ -49,7 +62,21 @@ public void CopyPropertiesToNewTest()
Assert.AreEqual(source.StringData, destination.StringData);
Assert.AreEqual(source.StringNull, destination.StringNull);
}


[Test]
public void CopyOnlyPropertiesToNewTest()
{
var source = BasicJson.GetDefault();
string[] Only = { "BoolData", "DecimalData" };
var destination = source.CopyOnlyPropertiesToNew<BasicJson>(Only);

Assert.IsNotNull(destination);
Assert.AreSame(source.GetType(), destination.GetType());

Assert.AreEqual(source.BoolData, destination.BoolData);
Assert.AreEqual(source.DecimalData, destination.DecimalData);
}

[Test]
public void ActionRetryTest()
{
Expand Down
2 changes: 1 addition & 1 deletion test/Unosquare.Swan.Test/ObjectMapperTest.cs
Expand Up @@ -65,7 +65,7 @@ public void AutoMapTest()
Assert.IsNotNull(destination);
Assert.AreEqual(_sourceUser.Name, destination.Name);
Assert.AreEqual(_sourceUser.Email, destination.Email);
Assert.IsNull(destination.Role);
Assert.IsNotNull(destination.Role);
}

[Test]
Expand Down

0 comments on commit 3ac460d

Please sign in to comment.