using Newtonsoft.Json; using System.Collections.Concurrent; using System.Reflection; namespace zero.Utils; public class ObjectCopycat { const string SEPARATOR = "."; static readonly Type STRING_TYPE = typeof(string); static ConcurrentDictionary> PublicPropertiesPerType { get; set; } = new(); public static T Clone(T obj) { Type type = obj.GetType(); return (T)JsonConvert.DeserializeObject(JsonConvert.SerializeObject(obj), type); } public static T CopyProperties(T source, T target, params string[] exceptions) { return CopyPropertiesInternal(source, target, null, exceptions); } static T CopyPropertiesInternal(T source, T target, string keyPrefix = null, params string[] exceptions) { Type type = source.GetType(); if (!PublicPropertiesPerType.TryGetValue(type.FullName, out IEnumerable properties)) { properties = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Where(x => x.CanWrite); PublicPropertiesPerType.TryAdd(type.FullName, properties); } foreach (PropertyInfo property in properties) { string propertyNameWithPrefix = CombineKey(SEPARATOR, keyPrefix, property.Name); if (exceptions.Contains(propertyNameWithPrefix, StringComparer.InvariantCultureIgnoreCase)) { continue; } object propertyValue = property.GetValue(source, null); if (property.PropertyType.IsValueType || property.PropertyType.IsEnum || property.PropertyType.Equals(STRING_TYPE) || propertyValue == null || propertyValue is IEnumerable) { property.SetValue(target, propertyValue, null); } else if (property.PropertyType.IsClass) { string[] nestedExceptions = exceptions.Where(x => x.StartsWith(propertyNameWithPrefix, StringComparison.InvariantCultureIgnoreCase)).ToArray(); property.SetValue(target, CopyPropertiesInternal(propertyValue, property.GetValue(target, null), propertyNameWithPrefix, nestedExceptions), null); } } return target; } private static string CombineKey(string sep, string key1, string key2) { if (String.IsNullOrEmpty(key1)) { return key2; } return String.Join(sep, key1, key2); } }