Skip to content

Commit

Permalink
Object Comparer and release 0.14.8
Browse files Browse the repository at this point in the history
  • Loading branch information
geoperez committed Jul 14, 2017
1 parent ef6b0f4 commit 86cdf87
Show file tree
Hide file tree
Showing 8 changed files with 318 additions and 130 deletions.
107 changes: 107 additions & 0 deletions src/Unosquare.Swan/Components/ObjectComparer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Unosquare.Swan.Reflection;

namespace Unosquare.Swan.Components
{
/// <summary>
/// Represents a quick object comparer using the public properties of an object
/// or the public members in a struct
/// </summary>
public static class ObjectComparer
{
private static readonly PropertyTypeCache PropertyTypeCache = new PropertyTypeCache();
private static readonly FieldTypeCache FieldTypeCache = new FieldTypeCache();

#region Private API

/// <summary>
/// Retrieves PropertyInfo[] (both public and non-public) for the given type
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
private static PropertyInfo[] RetrieveProperties<T>()
{
return PropertyTypeCache.Retrieve(typeof(T), () =>
{
return typeof(T).GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
.Where(p => p.CanRead || p.CanWrite).ToArray();
});
}

/// <summary>
/// Retrieves FieldInfo[] (public) for the given type
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
private static FieldInfo[] RetrieveFields<T>()
{
return FieldTypeCache.Retrieve(typeof(T),
() => typeof(T).GetFields(BindingFlags.Public | BindingFlags.Instance).ToArray());
}

#endregion

/// <summary>
/// Compare if two object of the same type are equal.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="left">The left.</param>
/// <param name="right">The right.</param>
/// <returns></returns>
public static bool AreEqual<T>(T left, T right) where T : class
{
var properties = RetrieveProperties<T>().ToArray();

foreach (var propertyTarget in properties)
{
var targetPropertyGetMethod = propertyTarget.GetGetMethod();

if (object.Equals(targetPropertyGetMethod.Invoke(left, null), targetPropertyGetMethod.Invoke(right, null)) == false)
return false;
}

return true;
}

/// <summary>
/// Compare if two structs of the same type are equal.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="left">The left.</param>
/// <param name="right">The right.</param>
/// <returns></returns>
public static bool AreStructsEqual<T>(T left, T right) where T : struct
{
var fields = new List<MemberInfo>();

if (typeof(T).IsValueType())
{
fields.AddRange(RetrieveFields<T>());
}

fields.AddRange(RetrieveProperties<T>().ToArray());

foreach (var targetMember in fields)
{
var targetField = (targetMember as FieldInfo);

if (targetField != null)
{
if (targetField.GetValue(left).Equals(targetField.GetValue(right)) == false)
return false;
}
else
{
var targetPropertyGetMethod = (targetMember as PropertyInfo).GetGetMethod();

if (object.Equals(targetPropertyGetMethod.Invoke(left, null), targetPropertyGetMethod.Invoke(right, null)) == false)
return false;
}
}

return true;
}
}
}
108 changes: 62 additions & 46 deletions src/Unosquare.Swan/Formatters/Json.Serializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -226,53 +226,12 @@ private Serializer(object obj, int depth, bool format, string typeSpecifier, str
#endregion

#region All Other Types Handling

{
// If we arrive here, then we convert the object into a
// dictionary of property names and values and call the serialization
// function again

// Create the dictionary and extract the properties
var objectDictionary = new Dictionary<string, object>();

var fields = new List<MemberInfo>();

// If the target is a struct (value type) navigate the fields.
if (targetType.IsValueType())
{
fields.AddRange(RetrieveFields(targetType));
}

// then incorporate the properties
fields.AddRange(RetrieveProperties(targetType).Where(p => p.CanRead).ToArray());

// If we set the included properties, then we remove everything that is not listed
if (IncludeProperties.Count > 0)
fields = fields.Where(p => IncludeProperties.Contains(p.Name)).ToList();

if (string.IsNullOrWhiteSpace(typeSpecifier) == false)
objectDictionary[typeSpecifier] = targetType.ToString();

foreach (var field in fields)
{
// Skip over the excluded properties
if (ExcludeProperties.Count > 0 && ExcludeProperties.Contains(field.Name))
continue;

// Build the dictionary using property names and values
// Note: used to be: property.GetValue(target); but we would be reading private properties
try
{
objectDictionary[
field.GetCustomAttribute<JsonPropertyAttribute>()?.PropertyName ?? field.Name] =
field is PropertyInfo
? (field as PropertyInfo).GetGetMethod(includeNonPublic)?.Invoke(target, null)
: (field as FieldInfo).GetValue(target);
}
catch // (Exception ex)
{
/* ignored */
}
}
var objectDictionary = CreateDictionary(typeSpecifier, includeNonPublic, targetType, target);

// At this point we either have a dictionary with or without properties
// If we have at least one property then we send it through the serialization method
Expand Down Expand Up @@ -307,6 +266,62 @@ public static string Serialize(object obj, int depth, bool format, string typeSe

#region Helper Methods

/// <summary>
/// Creates the dictionary of values from a target.
/// </summary>
/// <param name="typeSpecifier">The type specifier.</param>
/// <param name="includeNonPublic">if set to <c>true</c> [include non public].</param>
/// <param name="targetType">Type of the target.</param>
/// <param name="target">The target.</param>
/// <returns></returns>
private Dictionary<string, object> CreateDictionary(string typeSpecifier, bool includeNonPublic, Type targetType, object target)
{
// Create the dictionary and extract the properties
var objectDictionary = new Dictionary<string, object>();

var fields = new List<MemberInfo>();

// If the target is a struct (value type) navigate the fields.
if (targetType.IsValueType())
{
fields.AddRange(RetrieveFields(targetType));
}

// then incorporate the properties
fields.AddRange(RetrieveProperties(targetType).Where(p => p.CanRead).ToArray());

// If we set the included properties, then we remove everything that is not listed
if (IncludeProperties.Count > 0)
fields = fields.Where(p => IncludeProperties.Contains(p.Name)).ToList();

if (string.IsNullOrWhiteSpace(typeSpecifier) == false)
objectDictionary[typeSpecifier] = targetType.ToString();

foreach (var field in fields)
{
// Skip over the excluded properties
if (ExcludeProperties.Count > 0 && ExcludeProperties.Contains(field.Name))
continue;

// Build the dictionary using property names and values
// Note: used to be: property.GetValue(target); but we would be reading private properties
try
{
objectDictionary[
field.GetCustomAttribute<JsonPropertyAttribute>()?.PropertyName ?? field.Name] =
field is PropertyInfo
? (field as PropertyInfo).GetGetMethod(includeNonPublic)?.Invoke(target, null)
: (field as FieldInfo).GetValue(target);
}
catch // (Exception ex)
{
/* ignored */
}
}

return objectDictionary;
}

/// <summary>
/// Gets the indent string given the depth.
/// </summary>
Expand Down Expand Up @@ -353,9 +368,10 @@ private void RemoveLastComma()
if (Builder.Length < LastCommaSearch.Length)
return;

for (var i = 0; i < LastCommaSearch.Length; i++)
if (Builder[Builder.Length - LastCommaSearch.Length + i] != LastCommaSearch[i])
return;
if (LastCommaSearch.Where((t, i) => Builder[Builder.Length - LastCommaSearch.Length + i] != t).Any())
{
return;
}

// If we got this far, we simply remove the comma character
Builder.Remove(Builder.Length - LastCommaSearch.Length, 1);
Expand Down
133 changes: 66 additions & 67 deletions src/Unosquare.Swan/Formatters/Json.cs
Original file line number Diff line number Diff line change
Expand Up @@ -204,73 +204,7 @@ private static object ConvertFromJsonResult(object source, Type targetType, ref

else
{
var fields = new List<MemberInfo>();

if (targetType.IsValueType())
{
fields.AddRange(RetrieveFields(targetType));
}
fields.AddRange(RetrieveProperties(targetType).Where(p => p.CanRead).ToArray());



//var targetProperties = RetrieveProperties(targetType); //.Where(p => p.CanWrite);
foreach (var targetProperty in fields)
{
var targetPropertyName = targetProperty.GetCustomAttribute<JsonPropertyAttribute>()?.PropertyName ?? targetProperty.Name;
var sourcePropertyValue = sourceProperties.ContainsKey(targetPropertyName)
? sourceProperties[targetPropertyName]
: null;

if (sourcePropertyValue == null) continue;

object currentPropertyValue = null;

if (!targetType.IsValueType())
{
if ((targetProperty as PropertyInfo).PropertyType.IsArray == false)
{
try
{
currentPropertyValue = (targetProperty as PropertyInfo).GetGetMethod(includeNonPublic)?
.Invoke(target, null);
}
catch
{
// ignored
}
}
}

try
{

if (targetType.IsValueType())
{
var targetPropertyValue = ConvertFromJsonResult(sourcePropertyValue,
(targetProperty as FieldInfo).FieldType, ref currentPropertyValue, includeNonPublic);

(targetProperty as FieldInfo).SetValue(target, targetPropertyValue);
}
else
{
// Try to write properties to the current property value as a reference to the current property value
var targetPropertyValue = ConvertFromJsonResult(sourcePropertyValue,
(targetProperty as PropertyInfo).PropertyType, ref currentPropertyValue, includeNonPublic);

// HACK: Always try to write the value of possible; otherwise it was most likely (hopefully) set by reference
// if (currentPropertyValue == null || targetProperty.PropertyType == typeof(string) || targetProperty.PropertyType.IsValueType())

(targetProperty as PropertyInfo).GetSetMethod(includeNonPublic)?
.Invoke(target, new[] { targetPropertyValue });
}
}
catch
{

// ignored
}
}
PopulateObject(targetType, includeNonPublic, sourceProperties, target);
}

#endregion
Expand Down Expand Up @@ -381,6 +315,71 @@ private static object ConvertFromJsonResult(object source, Type targetType, ref
#endregion
}

private static void PopulateObject(Type targetType, bool includeNonPublic, Dictionary<string, object> sourceProperties, object target)
{
var fields = new List<MemberInfo>();

if (targetType.IsValueType())
{
fields.AddRange(RetrieveFields(targetType));
}

fields.AddRange(RetrieveProperties(targetType).Where(p => p.CanWrite).ToArray());

foreach (var targetProperty in fields)
{
var targetPropertyName = targetProperty.GetCustomAttribute<JsonPropertyAttribute>()?.PropertyName ??
targetProperty.Name;
var sourcePropertyValue = sourceProperties.ContainsKey(targetPropertyName)
? sourceProperties[targetPropertyName]
: null;

if (sourcePropertyValue == null) continue;

object currentPropertyValue = null;

if (!targetType.IsValueType() && (targetProperty as PropertyInfo).PropertyType.IsArray == false)
{
try
{
currentPropertyValue = (targetProperty as PropertyInfo).GetGetMethod(includeNonPublic)?
.Invoke(target, null);
}
catch
{
// ignored
}
}

try
{
if (targetType.IsValueType())
{
var targetPropertyValue = ConvertFromJsonResult(sourcePropertyValue,
(targetProperty as FieldInfo).FieldType, ref currentPropertyValue, includeNonPublic);

(targetProperty as FieldInfo).SetValue(target, targetPropertyValue);
}
else
{
// Try to write properties to the current property value as a reference to the current property value
var targetPropertyValue = ConvertFromJsonResult(sourcePropertyValue,
(targetProperty as PropertyInfo).PropertyType, ref currentPropertyValue, includeNonPublic);

// HACK: Always try to write the value of possible; otherwise it was most likely (hopefully) set by reference
// if (currentPropertyValue == null || targetProperty.PropertyType == typeof(string) || targetProperty.PropertyType.IsValueType())

(targetProperty as PropertyInfo).GetSetMethod(includeNonPublic)?
.Invoke(target, new[] {targetPropertyValue});
}
}
catch
{
// ignored
}
}
}

#endregion

#region Public API
Expand Down
Loading

0 comments on commit 86cdf87

Please sign in to comment.