using System.Reflection;
namespace zero.Utils;
public class ObjectTraverser
{
private const string SEPARATOR = ".";
private const string SQBKT_OPEN = "[";
private const string SQBKT_CLOSE = "]";
private const string NEWTONSOFT_NS = "Newtonsoft.Json";
///
/// Find all instances of a given type in an object (features nested + enumerable content)
///
public static List> Find(object value) where T : class
{
HashSet exploredObjects = new HashSet();
List> found = new List>();
Find(value, null, String.Empty, null, exploredObjects, found);
return found;
}
///
/// Find all instances of a given type in an object (features nested + enumerable content)
///
public static List Find(Type type, object value)
{
HashSet exploredObjects = new HashSet();
List found = new List();
Find(type, value, null, String.Empty, null, exploredObjects, found);
return found;
}
///
/// Find all instances of a given type in an object (features nested + enumerable content)
///
public static List> FindAttribute(object value) where T : Attribute
{
HashSet exploredObjects = new HashSet();
List> found = new List>();
FindAttribute(value, null, String.Empty, null, exploredObjects, found);
return found;
}
///
/// Recursively ind all instances of a given type in an object
///
private static void Find(object value, object parent, string key, PropertyInfo property, HashSet exploredObjects, List> found) where T : class
{
if (value == null || value is string || exploredObjects.Contains(value) || value.GetType().FullName.StartsWith(NEWTONSOFT_NS))
{
return;
}
exploredObjects.Add(value);
System.Collections.ICollection enumerable = value as System.Collections.ICollection;
// this property is a list
if (enumerable != null)
{
int idx = 0;
foreach (object item in enumerable)
{
Find(item, value, CombineKey(String.Empty, key, SQBKT_OPEN + idx + SQBKT_CLOSE), property, exploredObjects, found);
idx += 1;
}
return;
}
// get type of value
Type type = typeof(T);
Type valueType = value.GetType();
Type genericValueType = valueType.IsGenericType ? valueType.GetGenericTypeDefinition() : null;
// check if the current property is our searched type
if (valueType == type || genericValueType == type || type.IsAssignableFrom(valueType) || (genericValueType != null && type.IsAssignableFrom(genericValueType)))
{
found.Add(new Result()
{
Path = key,
Item = value as T,
Parent = parent,
Property = property
});
return;
}
// check if our property is a primitive type
if (value == null || valueType.IsPrimitive || value is string || value is DateTime || value is DateTimeOffset || value is Uri)
{
return;
}
// iterate over nested properties
PropertyInfo[] properties = valueType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty);
foreach (PropertyInfo nestedProperty in properties)
{
object propertyValue = nestedProperty.GetValue(value, null);
Find(propertyValue, value, CombineKey(SEPARATOR, key, nestedProperty.Name), nestedProperty, exploredObjects, found);
}
}
///
/// Recursively ind all instances of a given type in an object
///
private static void Find(Type type, object value, object parent, string key, PropertyInfo property, HashSet exploredObjects, List found)
{
if (value == null || value is string || exploredObjects.Contains(value) || value.GetType().FullName.StartsWith(NEWTONSOFT_NS))
{
return;
}
exploredObjects.Add(value);
System.Collections.ICollection enumerable = value as System.Collections.ICollection;
// this property is a list
if (enumerable != null)
{
int idx = 0;
foreach (object item in enumerable)
{
Find(type, item, value, CombineKey(String.Empty, key, SQBKT_OPEN + idx + SQBKT_CLOSE), property, exploredObjects, found);
idx += 1;
}
return;
}
// get type of value
Type valueType = value.GetType();
Type genericValueType = valueType.IsGenericType ? valueType.GetGenericTypeDefinition() : null;
// check if the current property is our searched type
if (valueType == type || genericValueType == type || type.IsAssignableFrom(valueType) || (genericValueType != null && type.IsAssignableFrom(genericValueType)))
{
found.Add(new Result()
{
Path = key,
Item = value,
Type = valueType,
Parent = parent,
Property = property
});
return;
}
// check if our property is a primitive type
if (value == null || valueType.IsPrimitive || value is string || value is DateTime || value is DateTimeOffset || value is Uri)
{
return;
}
// iterate over nested properties
PropertyInfo[] properties = valueType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty);
foreach (PropertyInfo nestedProperty in properties)
{
object propertyValue = nestedProperty.GetValue(value, null);
Find(type, propertyValue, value, CombineKey(SEPARATOR, key, nestedProperty.Name), nestedProperty, exploredObjects, found);
}
}
///
/// Recursively find all instances with the attribute of a given type in an object
///
private static void FindAttribute(object value, object parent, string key, PropertyInfo property, HashSet exploredObjects, List> found) where T : Attribute
{
if (exploredObjects.Contains(key) || (value != null && value.GetType().FullName.StartsWith(NEWTONSOFT_NS)))
{
return;
}
exploredObjects.Add(key);
// check if the current property contains the attribute
if (property != null)
{
T attribute = property.GetCustomAttribute(true);
if (attribute != null)
{
found.Add(new Result()
{
Path = key,
Item = attribute,
Parent = parent,
Property = property //parent.GetType().GetProperty(key)
});
return;
}
}
System.Collections.ICollection enumerable = value as System.Collections.ICollection;
// this property is a list
if (enumerable != null && !(value is string))
{
int idx = 0;
foreach (object item in enumerable)
{
FindAttribute(item, value, CombineKey(String.Empty, key, SQBKT_OPEN + idx + SQBKT_CLOSE), null, exploredObjects, found);
idx += 1;
}
return;
}
// check if our property is a primitive type
if (value == null || value.GetType().IsPrimitive || value is string || value is DateTime || value is DateTimeOffset || value is Uri || value is System.Text.Json.JsonElement)
{
return;
}
// iterate over nested properties
PropertyInfo[] properties = value.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty);
foreach (PropertyInfo nestedProperty in properties)
{
object propertyValue = nestedProperty.GetValue(value, null);
FindAttribute(propertyValue, value, CombineKey(SEPARATOR, key, nestedProperty.Name), nestedProperty, exploredObjects, found);
}
}
private static string CombineKey(string sep, string key1, string key2)
{
if (String.IsNullOrEmpty(key1))
{
return key2;
}
return String.Join(sep, key1, key2);
}
public class Result
{
public string Path { get; set; }
public PropertyInfo Property { get; set; }
public object Parent { get; set; }
public T Item { get; set; }
}
public class Result
{
public string Path { get; set; }
public PropertyInfo Property { get; set; }
public object Parent { get; set; }
public object Item { get; set; }
public Type Type { get; set; }
public int Index { get; set; }
}
}