diff --git a/ObjectPrinting/Actions/ActionResult.cs b/ObjectPrinting/Actions/ActionResult.cs new file mode 100644 index 000000000..84cb74d23 --- /dev/null +++ b/ObjectPrinting/Actions/ActionResult.cs @@ -0,0 +1,7 @@ +namespace ObjectPrinting.Actions; + +public class ActionResult +{ + public string Value { get; set; } + public bool IsSkipped { get; set; } +} \ No newline at end of file diff --git a/ObjectPrinting/Actions/ExcludeAction.cs b/ObjectPrinting/Actions/ExcludeAction.cs new file mode 100644 index 000000000..83b6420f6 --- /dev/null +++ b/ObjectPrinting/Actions/ExcludeAction.cs @@ -0,0 +1,34 @@ +using System; +using System.Reflection; + +namespace ObjectPrinting.Actions; + +public class ExcludeAction : IAction +{ + private readonly Type? typeToExclude = null; + private readonly PropertyInfo? propertyToExclude = null; + + public ExcludeAction(Type typeToExclude) + { + this.typeToExclude = typeToExclude; + } + + public ExcludeAction(PropertyInfo propertyToExclude) + { + this.propertyToExclude = propertyToExclude; + } + + public bool CanHandle(PropertyInfo property) + { + return propertyToExclude != null && propertyToExclude == property || + typeToExclude != null && typeToExclude.IsAssignableFrom(property.PropertyType); + } + + public ActionResult Handle(object value) + { + return new ActionResult + { + IsSkipped = true + }; + } +} \ No newline at end of file diff --git a/ObjectPrinting/Actions/IAction.cs b/ObjectPrinting/Actions/IAction.cs new file mode 100644 index 000000000..969238f59 --- /dev/null +++ b/ObjectPrinting/Actions/IAction.cs @@ -0,0 +1,9 @@ +using System.Reflection; + +namespace ObjectPrinting.Actions; + +public interface IAction +{ + bool CanHandle(PropertyInfo property); + ActionResult Handle(object value); +} \ No newline at end of file diff --git a/ObjectPrinting/Actions/SerializeAction.cs b/ObjectPrinting/Actions/SerializeAction.cs new file mode 100644 index 000000000..e9dea7c24 --- /dev/null +++ b/ObjectPrinting/Actions/SerializeAction.cs @@ -0,0 +1,39 @@ +using System; +using System.Reflection; + +namespace ObjectPrinting.Actions; + +public class SerializeAction : IAction +{ + private readonly Func serializer; + + private readonly Type? typeToSerialize = null; + private readonly PropertyInfo? propertyToSerialize = null; + + public SerializeAction(Type typeToSerialize, Func serializer) + { + this.typeToSerialize = typeToSerialize; + this.serializer = serializer; + } + + public SerializeAction(PropertyInfo propertyToSerialize, Func serializer) + { + this.propertyToSerialize = propertyToSerialize; + this.serializer = serializer; + } + + public bool CanHandle(PropertyInfo property) + { + return propertyToSerialize != null && propertyToSerialize == property || + typeToSerialize != null && typeToSerialize.IsAssignableFrom(property.PropertyType); + } + + public ActionResult Handle(object value) + { + return new ActionResult + { + IsSkipped = false, + Value = serializer(value) + }; + } +} \ No newline at end of file diff --git a/ObjectPrinting/Actions/SpecifyCultureAction.cs b/ObjectPrinting/Actions/SpecifyCultureAction.cs new file mode 100644 index 000000000..9ebc41d63 --- /dev/null +++ b/ObjectPrinting/Actions/SpecifyCultureAction.cs @@ -0,0 +1,41 @@ +using System; +using System.Globalization; +using System.Reflection; + +namespace ObjectPrinting.Actions; + +public class SpecifyCultureAction : IAction +{ + private readonly Type? typeToFormat = null; + private readonly PropertyInfo? propertyToFormat = null; + private readonly CultureInfo cultureInfo; + + public SpecifyCultureAction(Type typeToFormat, CultureInfo cultureInfo) + { + this.typeToFormat = typeToFormat; + this.cultureInfo = cultureInfo; + } + + public SpecifyCultureAction(PropertyInfo propertyToFormat, CultureInfo cultureInfo) + { + this.propertyToFormat = propertyToFormat; + this.cultureInfo = cultureInfo; + } + + + public bool CanHandle(PropertyInfo property) + { + return propertyToFormat != null && propertyToFormat == property && + typeof(IFormattable).IsAssignableFrom(property.PropertyType) || + typeToFormat != null && typeToFormat.IsAssignableFrom(property.PropertyType); + } + + public ActionResult Handle(object value) + { + return new ActionResult + { + IsSkipped = false, + Value = ((IFormattable)value).ToString(null, cultureInfo) + }; + } +} \ No newline at end of file diff --git a/ObjectPrinting/Actions/TrimAction.cs b/ObjectPrinting/Actions/TrimAction.cs new file mode 100644 index 000000000..56a38aaf1 --- /dev/null +++ b/ObjectPrinting/Actions/TrimAction.cs @@ -0,0 +1,32 @@ +using System; +using System.Reflection; + +namespace ObjectPrinting.Actions; + +public class TrimAction : IAction +{ + private readonly int maxLength; + private readonly PropertyInfo propertyToTrim; + + public TrimAction(PropertyInfo propertyToTrim, int maxLength) + { + ArgumentOutOfRangeException.ThrowIfNegative(maxLength); + + this.propertyToTrim = propertyToTrim; + this.maxLength = maxLength; + } + + public bool CanHandle(PropertyInfo property) + { + return propertyToTrim == property && property.PropertyType == typeof(string); + } + + public ActionResult Handle(object value) + { + return new ActionResult + { + IsSkipped = false, + Value = ((string)value)[..maxLength] + }; + } +} \ No newline at end of file diff --git a/ObjectPrinting/Expected/ObjectPrinter_CustomPropertySerializer.txt b/ObjectPrinting/Expected/ObjectPrinter_CustomPropertySerializer.txt new file mode 100644 index 000000000..41e0f618a --- /dev/null +++ b/ObjectPrinting/Expected/ObjectPrinter_CustomPropertySerializer.txt @@ -0,0 +1,11 @@ +Person + Id = 00000000-0000-0000-0000-000000000000 + Name = Alex + Surname = null + Height = 0 + Age = 19 years old + LastTimeOnline = 01.01.0001 0:00:00 + Father = null + Child = null + Tags = null + CustomProperties = null diff --git a/ObjectPrinting/Expected/ObjectPrinter_CustomTypeSerializer.txt b/ObjectPrinting/Expected/ObjectPrinter_CustomTypeSerializer.txt new file mode 100644 index 000000000..708d6bd80 --- /dev/null +++ b/ObjectPrinting/Expected/ObjectPrinter_CustomTypeSerializer.txt @@ -0,0 +1,11 @@ +Person + Id = 00000000-0000-0000-0000-000000000000 + Name = Alex - string + Surname = Brown - string + Height = 2005 + Age = 19 + LastTimeOnline = 01.01.0001 0:00:00 + Father = null + Child = null + Tags = null + CustomProperties = null diff --git a/ObjectPrinting/Expected/ObjectPrinter_ExcludeProperty.txt b/ObjectPrinting/Expected/ObjectPrinter_ExcludeProperty.txt new file mode 100644 index 000000000..7e8d84a30 --- /dev/null +++ b/ObjectPrinting/Expected/ObjectPrinter_ExcludeProperty.txt @@ -0,0 +1,10 @@ +Person + Id = 00000000-0000-0000-0000-000000000000 + Name = Alex + Surname = null + Height = 0 + LastTimeOnline = 01.01.0001 0:00:00 + Father = null + Child = null + Tags = null + CustomProperties = null diff --git a/ObjectPrinting/Expected/ObjectPrinter_ExcludeType.txt b/ObjectPrinting/Expected/ObjectPrinter_ExcludeType.txt new file mode 100644 index 000000000..92fa348b1 --- /dev/null +++ b/ObjectPrinting/Expected/ObjectPrinter_ExcludeType.txt @@ -0,0 +1,10 @@ +Person + Name = Alex + Surname = null + Height = 0 + Age = 19 + LastTimeOnline = 01.01.0001 0:00:00 + Father = null + Child = null + Tags = null + CustomProperties = null diff --git a/ObjectPrinting/Expected/ObjectPrinter_ICollection.txt b/ObjectPrinting/Expected/ObjectPrinter_ICollection.txt new file mode 100644 index 000000000..db4a9d37a --- /dev/null +++ b/ObjectPrinting/Expected/ObjectPrinter_ICollection.txt @@ -0,0 +1,14 @@ +Person + Id = 00000000-0000-0000-0000-000000000000 + Name = Alex + Surname = null + Height = 0 + Age = 19 + LastTimeOnline = 01.01.0001 0:00:00 + Father = null + Child = null + Tags = [ + [0] = tag1 + [1] = tag2 + ] + CustomProperties = null diff --git a/ObjectPrinting/Expected/ObjectPrinter_IDictionary.txt b/ObjectPrinting/Expected/ObjectPrinter_IDictionary.txt new file mode 100644 index 000000000..39ac3af93 --- /dev/null +++ b/ObjectPrinting/Expected/ObjectPrinter_IDictionary.txt @@ -0,0 +1,14 @@ +Person + Id = 00000000-0000-0000-0000-000000000000 + Name = Alex + Surname = null + Height = 0 + Age = 19 + LastTimeOnline = 01.01.0001 0:00:00 + Father = null + Child = null + Tags = null + CustomProperties = { + PhoneNumber = 88005553535 + HomeAddress = Novokoltsovo + } diff --git a/ObjectPrinting/Expected/ObjectPrinter_NoStackOverflow.txt b/ObjectPrinting/Expected/ObjectPrinter_NoStackOverflow.txt new file mode 100644 index 000000000..83756bd15 --- /dev/null +++ b/ObjectPrinting/Expected/ObjectPrinter_NoStackOverflow.txt @@ -0,0 +1,21 @@ +Person + Id = 00000000-0000-0000-0000-000000000000 + Name = Alex + Surname = null + Height = 0 + Age = 19 + LastTimeOnline = 01.01.0001 0:00:00 + Father = Person + Id = 00000000-0000-0000-0000-000000000000 + Name = John + Surname = null + Height = 0 + Age = 42 + LastTimeOnline = 01.01.0001 0:00:00 + Father = null + Child = cyclic reference + Tags = null + CustomProperties = null + Child = null + Tags = null + CustomProperties = null diff --git a/ObjectPrinting/Expected/ObjectPrinter_SpecifyPropertyCulture.txt b/ObjectPrinting/Expected/ObjectPrinter_SpecifyPropertyCulture.txt new file mode 100644 index 000000000..1e0fe59ad --- /dev/null +++ b/ObjectPrinting/Expected/ObjectPrinter_SpecifyPropertyCulture.txt @@ -0,0 +1,11 @@ +Person + Id = 00000000-0000-0000-0000-000000000000 + Name = Alex + Surname = null + Height = 185.12 + Age = 19 + LastTimeOnline = 01.01.0001 0:00:00 + Father = null + Child = null + Tags = null + CustomProperties = null diff --git a/ObjectPrinting/Expected/ObjectPrinter_SpecifyTypeCulture.txt b/ObjectPrinting/Expected/ObjectPrinter_SpecifyTypeCulture.txt new file mode 100644 index 000000000..b65b42eab --- /dev/null +++ b/ObjectPrinting/Expected/ObjectPrinter_SpecifyTypeCulture.txt @@ -0,0 +1,11 @@ +Person + Id = 00000000-0000-0000-0000-000000000000 + Name = Alex + Surname = null + Height = 0 + Age = 19 + LastTimeOnline = 1/1/2021 12:00:00 PM + Father = null + Child = null + Tags = null + CustomProperties = null diff --git a/ObjectPrinting/Expected/ObjectPrinter_TrimProperty.txt b/ObjectPrinting/Expected/ObjectPrinter_TrimProperty.txt new file mode 100644 index 000000000..69fe92731 --- /dev/null +++ b/ObjectPrinting/Expected/ObjectPrinter_TrimProperty.txt @@ -0,0 +1,11 @@ +Person + Id = 00000000-0000-0000-0000-000000000000 + Name = Alex + Surname = VeryLongSu + Height = 0 + Age = 19 + LastTimeOnline = 01.01.0001 0:00:00 + Father = null + Child = null + Tags = null + CustomProperties = null diff --git a/ObjectPrinting/ObjectPrinter.cs b/ObjectPrinting/ObjectPrinter.cs index 3c7867c32..7f9ce2efd 100644 --- a/ObjectPrinting/ObjectPrinter.cs +++ b/ObjectPrinting/ObjectPrinter.cs @@ -1,10 +1,128 @@ -namespace ObjectPrinting +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace ObjectPrinting; + +public class ObjectPrinter { - public class ObjectPrinter + private readonly PrintingConfig config; + private readonly HashSet processed = []; + + public static PrintingConfigurator For() { - public static PrintingConfig For() + return new PrintingConfigurator(); + } + + public ObjectPrinter(PrintingConfig config) + { + this.config = config; + } + + public string PrintToString(TOwner obj) + { + return PrintToString(obj, 0); + } + + private string? ProcessCollection(ICollection collection, int nestingLevel) + { + var tabsCount = new string('\t', nestingLevel + 1); + var index = 0; + + var sb = new StringBuilder(); + sb.AppendLine("["); + foreach (var value in collection) + { + var result = PrintToString(value, nestingLevel + 1); + sb.Append($"{tabsCount}[{index}] = {result}"); + index++; + } + + sb.Append(new string('\t', nestingLevel) + ']'); + + return sb.ToString(); + } + + private string ProcessDictionary(IDictionary dictionary, int nestingLevel) + { + var tabsCount = new string('\t', nestingLevel + 1); + + var sb = new StringBuilder(); + sb.AppendLine("{"); + foreach (DictionaryEntry entry in dictionary) + { + var key = entry.Key; + var value = entry.Value; + sb.Append($"{tabsCount}{key} = {PrintToString(value, nestingLevel + 1)}"); + } + + sb.Append(new string('\t', nestingLevel) + '}'); + return sb.ToString(); + } + + private string? TryFinishProperty(object obj, int nestingLevel) + { + if (obj == null) + { + return "null"; + } + + if (processed.Contains(obj)) + { + return "cyclic reference"; + } + + if (obj.GetType().IsSimple()) + { + return obj.ToString(); + } + + if (obj is IDictionary dictionary) + { + return ProcessDictionary(dictionary, nestingLevel); + } + + if (obj is ICollection collection) + { + return ProcessCollection(collection, nestingLevel); + } + + return null; + } + + private string? ProcessProperty(PropertyInfo propertyInfo, object obj, int nestingLevel) + { + foreach (var action in config.Actions.Where(a => a.CanHandle(propertyInfo))) + { + var result = action.Handle(propertyInfo.GetValue(obj)); + return result.IsSkipped ? null : result.Value + Environment.NewLine; + } + + return PrintToString(propertyInfo.GetValue(obj), nestingLevel + 1); + } + + private string PrintToString(object obj, int nestingLevel) + { + var possibleResult = TryFinishProperty(obj, nestingLevel); + if (possibleResult != null) return possibleResult + Environment.NewLine; + + processed.Add(obj); + + var tabsCount = new string('\t', nestingLevel + 1); + var sb = new StringBuilder(); + var type = obj.GetType(); + sb.AppendLine(type.Name); + foreach (var propertyInfo in type.GetProperties()) { - return new PrintingConfig(); + var propertyResult = ProcessProperty(propertyInfo, obj, nestingLevel); + if (propertyResult is null) continue; + sb.Append(tabsCount + propertyInfo.Name + " = " + propertyResult); } + + return sb.ToString(); } } \ No newline at end of file diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index a9e082117..49ca75716 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -1,41 +1,22 @@ -using System; -using System.Linq; -using System.Text; +using System.Collections.Generic; +using System.Globalization; +using ObjectPrinting.Actions; -namespace ObjectPrinting -{ - public class PrintingConfig - { - public string PrintToString(TOwner obj) - { - return PrintToString(obj, 0); - } +namespace ObjectPrinting; - private string PrintToString(object obj, int nestingLevel) - { - //TODO apply configurations - if (obj == null) - return "null" + Environment.NewLine; +public class PrintingConfig +{ + private readonly List actions; - var finalTypes = new[] - { - typeof(int), typeof(double), typeof(float), typeof(string), - typeof(DateTime), typeof(TimeSpan) - }; - if (finalTypes.Contains(obj.GetType())) - return obj + Environment.NewLine; + public PrintingConfig() + { + actions = []; + } - var identation = new string('\t', nestingLevel + 1); - var sb = new StringBuilder(); - var type = obj.GetType(); - sb.AppendLine(type.Name); - foreach (var propertyInfo in type.GetProperties()) - { - sb.Append(identation + propertyInfo.Name + " = " + - PrintToString(propertyInfo.GetValue(obj), - nestingLevel + 1)); - } - return sb.ToString(); - } + public void AddAction(IAction action) + { + actions.Add(action); } + + public IList Actions => actions.AsReadOnly(); } \ No newline at end of file diff --git a/ObjectPrinting/PrintingConfigurator.cs b/ObjectPrinting/PrintingConfigurator.cs new file mode 100644 index 000000000..748c75cd5 --- /dev/null +++ b/ObjectPrinting/PrintingConfigurator.cs @@ -0,0 +1,77 @@ +using System; +using System.Globalization; +using System.Linq.Expressions; +using System.Reflection; +using ObjectPrinting.Actions; + +namespace ObjectPrinting; + +public class PrintingConfigurator +{ + private readonly PrintingConfig config = new(); + + private PropertyInfo GetPropertyInfo(Expression> expression) + { + var property = expression.Body as MemberExpression; + var propertyInfo = property?.Member as PropertyInfo; + return propertyInfo ?? throw new ArgumentException("Invalid expression"); + } + + public PrintingConfigurator Exclude() + { + var action = new ExcludeAction(typeof(T)); + config.AddAction(action); + return this; + } + + public PrintingConfigurator Exclude(Expression> expression) + { + var propertyInfo = GetPropertyInfo(expression); + var action = new ExcludeAction(propertyInfo); + config.AddAction(action); + return this; + } + + public PrintingConfigurator SpecifyCulture(CultureInfo newCulture) where T : IFormattable + { + var action = new SpecifyCultureAction(typeof(T), newCulture); + config.AddAction(action); + return this; + } + + public PrintingConfigurator SpecifyCulture(Expression> expression, + CultureInfo cultureInfo) + { + var propertyInfo = GetPropertyInfo(expression); + var action = new SpecifyCultureAction(propertyInfo, cultureInfo); + config.AddAction(action); + return this; + } + + public PrintingConfigurator AddSerializer(Func serializer) + { + var action = new SerializeAction(typeof(T), serializer); + config.AddAction(action); + return this; + } + + public PrintingConfigurator AddSerializer(Expression> expression, + Func serializer) + { + var propertyInfo = GetPropertyInfo(expression); + var action = new SerializeAction(propertyInfo, serializer); + config.AddAction(action); + return this; + } + + public PrintingConfigurator Trim(Expression> expression, int maxLength) + { + var property = expression.Body as MemberExpression; + var propertyInfo = property?.Member as PropertyInfo; + var action = new TrimAction(propertyInfo, maxLength); + config.AddAction(action); + return this; + } + + public ObjectPrinter Build() => new ObjectPrinter(config); +} \ No newline at end of file diff --git a/ObjectPrinting/Solved/ObjectExtensions.cs b/ObjectPrinting/Solved/ObjectExtensions.cs deleted file mode 100644 index b0c94553c..000000000 --- a/ObjectPrinting/Solved/ObjectExtensions.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace ObjectPrinting.Solved -{ - public static class ObjectExtensions - { - public static string PrintToString(this T obj) - { - return ObjectPrinter.For().PrintToString(obj); - } - } -} \ No newline at end of file diff --git a/ObjectPrinting/Solved/ObjectPrinter.cs b/ObjectPrinting/Solved/ObjectPrinter.cs deleted file mode 100644 index 540ee769c..000000000 --- a/ObjectPrinting/Solved/ObjectPrinter.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace ObjectPrinting.Solved -{ - public class ObjectPrinter - { - public static PrintingConfig For() - { - return new PrintingConfig(); - } - } -} \ No newline at end of file diff --git a/ObjectPrinting/Solved/PrintingConfig.cs b/ObjectPrinting/Solved/PrintingConfig.cs deleted file mode 100644 index 0ec5aeb2b..000000000 --- a/ObjectPrinting/Solved/PrintingConfig.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Linq; -using System.Linq.Expressions; -using System.Text; - -namespace ObjectPrinting.Solved -{ - public class PrintingConfig - { - public PropertyPrintingConfig Printing() - { - return new PropertyPrintingConfig(this); - } - - public PropertyPrintingConfig Printing(Expression> memberSelector) - { - return new PropertyPrintingConfig(this); - } - - public PrintingConfig Excluding(Expression> memberSelector) - { - return this; - } - - internal PrintingConfig Excluding() - { - return this; - } - - public string PrintToString(TOwner obj) - { - return PrintToString(obj, 0); - } - - private string PrintToString(object obj, int nestingLevel) - { - //TODO apply configurations - if (obj == null) - return "null" + Environment.NewLine; - - var finalTypes = new[] - { - typeof(int), typeof(double), typeof(float), typeof(string), - typeof(DateTime), typeof(TimeSpan) - }; - if (finalTypes.Contains(obj.GetType())) - return obj + Environment.NewLine; - - var identation = new string('\t', nestingLevel + 1); - var sb = new StringBuilder(); - var type = obj.GetType(); - sb.AppendLine(type.Name); - foreach (var propertyInfo in type.GetProperties()) - { - sb.Append(identation + propertyInfo.Name + " = " + - PrintToString(propertyInfo.GetValue(obj), - nestingLevel + 1)); - } - return sb.ToString(); - } - } -} \ No newline at end of file diff --git a/ObjectPrinting/Solved/PropertyPrintingConfig.cs b/ObjectPrinting/Solved/PropertyPrintingConfig.cs deleted file mode 100644 index a509697d1..000000000 --- a/ObjectPrinting/Solved/PropertyPrintingConfig.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Globalization; - -namespace ObjectPrinting.Solved -{ - public class PropertyPrintingConfig : IPropertyPrintingConfig - { - private readonly PrintingConfig printingConfig; - - public PropertyPrintingConfig(PrintingConfig printingConfig) - { - this.printingConfig = printingConfig; - } - - public PrintingConfig Using(Func print) - { - return printingConfig; - } - - public PrintingConfig Using(CultureInfo culture) - { - return printingConfig; - } - - PrintingConfig IPropertyPrintingConfig.ParentConfig => printingConfig; - } - - public interface IPropertyPrintingConfig - { - PrintingConfig ParentConfig { get; } - } -} \ No newline at end of file diff --git a/ObjectPrinting/Solved/PropertyPrintingConfigExtensions.cs b/ObjectPrinting/Solved/PropertyPrintingConfigExtensions.cs deleted file mode 100644 index dd3922394..000000000 --- a/ObjectPrinting/Solved/PropertyPrintingConfigExtensions.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; - -namespace ObjectPrinting.Solved -{ - public static class PropertyPrintingConfigExtensions - { - public static string PrintToString(this T obj, Func, PrintingConfig> config) - { - return config(ObjectPrinter.For()).PrintToString(obj); - } - - public static PrintingConfig TrimmedToLength(this PropertyPrintingConfig propConfig, int maxLen) - { - return ((IPropertyPrintingConfig)propConfig).ParentConfig; - } - - } -} \ No newline at end of file diff --git a/ObjectPrinting/Solved/Tests/ObjectPrinterAcceptanceTests.cs b/ObjectPrinting/Solved/Tests/ObjectPrinterAcceptanceTests.cs deleted file mode 100644 index ac52d5ee5..000000000 --- a/ObjectPrinting/Solved/Tests/ObjectPrinterAcceptanceTests.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Globalization; -using NUnit.Framework; - -namespace ObjectPrinting.Solved.Tests -{ - [TestFixture] - public class ObjectPrinterAcceptanceTests - { - [Test] - public void Demo() - { - var person = new Person { Name = "Alex", Age = 19 }; - - var printer = ObjectPrinter.For() - //1. Исключить из сериализации свойства определенного типа - .Excluding() - //2. Указать альтернативный способ сериализации для определенного типа - .Printing().Using(i => i.ToString("X")) - //3. Для числовых типов указать культуру - .Printing().Using(CultureInfo.InvariantCulture) - //4. Настроить сериализацию конкретного свойства - //5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств) - .Printing(p => p.Name).TrimmedToLength(10) - //6. Исключить из сериализации конкретного свойства - .Excluding(p => p.Age); - - string s1 = printer.PrintToString(person); - - //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию - string s2 = person.PrintToString(); - - //8. ...с конфигурированием - string s3 = person.PrintToString(s => s.Excluding(p => p.Age)); - Console.WriteLine(s1); - Console.WriteLine(s2); - Console.WriteLine(s3); - } - } -} \ No newline at end of file diff --git a/ObjectPrinting/Solved/Tests/Person.cs b/ObjectPrinting/Solved/Tests/Person.cs deleted file mode 100644 index 858ebbf8d..000000000 --- a/ObjectPrinting/Solved/Tests/Person.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace ObjectPrinting.Solved.Tests -{ - public class Person - { - public Guid Id { get; set; } - public string Name { get; set; } - public double Height { get; set; } - public int Age { get; set; } - } -} \ No newline at end of file diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs index 4c8b2445c..8de1e0416 100644 --- a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs +++ b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs @@ -1,27 +1,34 @@ -using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Globalization; +using NUnit.Framework; -namespace ObjectPrinting.Tests +namespace ObjectPrinting.Tests; + +[TestFixture] +public class ObjectPrinterAcceptanceTests { - [TestFixture] - public class ObjectPrinterAcceptanceTests + [Test] + public void Demo() { - [Test] - public void Demo() + var person = new Person { - var person = new Person { Name = "Alex", Age = 19 }; + Name = "Alex", + Age = 19, + Height = 185.12, + Tags = ["tag1", "tag2"], + CustomProperties = new Dictionary { { "key1", "value1" }, { "key2", "value2" } }, + }; - var printer = ObjectPrinter.For(); - //1. Исключить из сериализации свойства определенного типа - //2. Указать альтернативный способ сериализации для определенного типа - //3. Для числовых типов указать культуру - //4. Настроить сериализацию конкретного свойства - //5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств) - //6. Исключить из сериализации конкретного свойства - - string s1 = printer.PrintToString(person); + var printer = ObjectPrinter.For() + .Exclude() + .Exclude(p => p.Age) + .SpecifyCulture(p => p.LastTimeOnline, CultureInfo.GetCultureInfo("es-ES")) + .AddSerializer(p => p.Height, _ => "2005") + .Trim(p => p.Name, 2) + .Build(); - //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию - //8. ...с конфигурированием - } + var s1 = printer.PrintToString(person); + Console.WriteLine(s1); } } \ No newline at end of file diff --git a/ObjectPrinting/Tests/ObjectPrinterTests.cs b/ObjectPrinting/Tests/ObjectPrinterTests.cs new file mode 100644 index 000000000..36a2e9875 --- /dev/null +++ b/ObjectPrinting/Tests/ObjectPrinterTests.cs @@ -0,0 +1,236 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using NUnit.Framework; + +namespace ObjectPrinting.Tests; + +[TestFixture] +public class ObjectPrinterTests +{ + private static string GetExpectedFilePath() + { + var testDirectory = TestContext.CurrentContext.TestDirectory; + var projectDirectory = Directory.GetParent(testDirectory)?.Parent?.Parent?.FullName; + + var expectedDirectory = Path.Combine(projectDirectory, "Expected"); + var fileName = $"{TestContext.CurrentContext.Test.MethodName}.txt"; + return Path.Combine(expectedDirectory, fileName); + } + + private static string GetExpectedResult() => File.ReadAllText(GetExpectedFilePath()); + + private void AssertExpectedResult(string actual) + { + var expected = GetExpectedResult(); + + TestContext.WriteLine("Expected: "); + TestContext.WriteLine(expected); + TestContext.WriteLine("Actual: "); + TestContext.WriteLine(actual); + + Assert.That(actual, Is.EqualTo(expected)); + } + + private void GenerateExpectedFile(string expected) + { + var filePath = GetExpectedFilePath(); + Directory.CreateDirectory(Path.GetDirectoryName(filePath)); + File.WriteAllText(filePath, expected); + Assert.Inconclusive($"Generated expected result: {filePath}"); + } + + + [Test] + public void ObjectPrinter_ExcludeType() + { + var person = new Person + { + Name = "Alex", + Age = 19 + }; + + var printer = ObjectPrinter.For() + .Exclude() + .Build(); + + var actual = printer.PrintToString(person); + + AssertExpectedResult(actual); + } + + [Test] + public void ObjectPrinter_ExcludeProperty() + { + var person = new Person + { + Name = "Alex", + Age = 19 + }; + + var printer = ObjectPrinter.For() + .Exclude(p => p.Age) + .Build(); + + var actual = printer.PrintToString(person); + + AssertExpectedResult(actual); + } + + [Test] + public void ObjectPrinter_CustomTypeSerializer() + { + var person = new Person + { + Name = "Alex", + Surname = "Brown", + Age = 19, + Height = 2005 + }; + + var printer = ObjectPrinter.For() + .AddSerializer(s => $"{s} - string") + .Build(); + + var actual = printer.PrintToString(person); + + AssertExpectedResult(actual); + } + + [Test] + public void ObjectPrinter_CustomPropertySerializer() + { + var person = new Person + { + Name = "Alex", + Age = 19, + }; + + var printer = ObjectPrinter.For() + .AddSerializer(p => p.Age, a => $"{a} years old") + .Build(); + + var actual = printer.PrintToString(person); + + AssertExpectedResult(actual); + } + + [Test] + public void ObjectPrinter_TrimProperty() + { + var person = new Person + { + Name = "Alex", + Surname = "VeryLongSurnameWeDontWantToSee", + Age = 19, + }; + + var printer = ObjectPrinter.For() + .Trim(p => p.Surname, 10) + .Build(); + + var actual = printer.PrintToString(person); + + AssertExpectedResult(actual); + } + + [Test] + public void ObjectPrinter_NoStackOverflow() + { + var father = new Person() + { + Name = "John", + Age = 42, + }; + var person = new Person + { + Name = "Alex", + Age = 19, + Father = father + }; + father.Child = person; + + var printer = ObjectPrinter.For() + .Build(); + + var actual = printer.PrintToString(person); + + AssertExpectedResult(actual); + } + + [Test] + public void ObjectPrinter_ICollection() + { + var person = new Person + { + Name = "Alex", + Age = 19, + Tags = ["tag1", "tag2"], + }; + + var printer = ObjectPrinter.For() + .Build(); + + var actual = printer.PrintToString(person); + + AssertExpectedResult(actual); + } + + [Test] + public void ObjectPrinter_IDictionary() + { + var person = new Person + { + Name = "Alex", + Age = 19, + CustomProperties = new Dictionary() + { { "PhoneNumber", "88005553535" }, { "HomeAddress", "Novokoltsovo" } } + }; + + var printer = ObjectPrinter.For() + .Build(); + + var actual = printer.PrintToString(person); + + AssertExpectedResult(actual); + } + + [Test] + public void ObjectPrinter_SpecifyTypeCulture() + { + var person = new Person + { + Name = "Alex", + Age = 19, + LastTimeOnline = new DateTime(2021, 1, 1, 12, 0, 0) + }; + + var printer = ObjectPrinter.For() + .SpecifyCulture(CultureInfo.GetCultureInfo("en-US")) + .Build(); + + var actual = printer.PrintToString(person); + + AssertExpectedResult(actual); + } + + [Test] + public void ObjectPrinter_SpecifyPropertyCulture() + { + var person = new Person + { + Name = "Alex", + Age = 19, + Height = 185.12 + }; + + var printer = ObjectPrinter.For() + .SpecifyCulture(p => p.Height, CultureInfo.GetCultureInfo("en-US")) + .Build(); + + var actual = printer.PrintToString(person); + + AssertExpectedResult(actual); + } +} \ No newline at end of file diff --git a/ObjectPrinting/Tests/Person.cs b/ObjectPrinting/Tests/Person.cs index f95559554..8316c4112 100644 --- a/ObjectPrinting/Tests/Person.cs +++ b/ObjectPrinting/Tests/Person.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; namespace ObjectPrinting.Tests { @@ -6,7 +7,13 @@ public class Person { public Guid Id { get; set; } public string Name { get; set; } + public string Surname { get; set; } public double Height { get; set; } public int Age { get; set; } + public DateTime LastTimeOnline { get; set; } + public Person Father { get; set; } + public Person Child { get; set; } + public string[] Tags { get; set; } + public Dictionary CustomProperties { get; set; } } } \ No newline at end of file diff --git a/ObjectPrinting/UtilsExtensions.cs b/ObjectPrinting/UtilsExtensions.cs new file mode 100644 index 000000000..a8d40eeef --- /dev/null +++ b/ObjectPrinting/UtilsExtensions.cs @@ -0,0 +1,30 @@ +using System; +using System.Linq; + +namespace ObjectPrinting; + +public static class UtilsExtensions +{ + private static readonly Type[] simpleTypes = + [ + + typeof(int), + typeof(uint), + typeof(long), + typeof(ulong), + typeof(short), + typeof(ushort), + typeof(byte), + typeof(sbyte), + typeof(decimal), + typeof(double), + typeof(float), + typeof(char), + typeof(string), + typeof(DateTime), + typeof(TimeSpan), + typeof(Guid) + ]; + + public static bool IsSimple(this Type type) => simpleTypes.Contains(type); +} \ No newline at end of file