diff --git a/ObjectPrinting/MemberInfoExtension.cs b/ObjectPrinting/MemberInfoExtension.cs new file mode 100644 index 00000000..5d1f88b6 --- /dev/null +++ b/ObjectPrinting/MemberInfoExtension.cs @@ -0,0 +1,28 @@ +using System; +using System.Reflection; + +namespace ObjectPrinting +{ + public static class MemberInfoExtension + { + public static object GetMemberValue(this MemberInfo memberInfo, object obj) + { + return memberInfo switch + { + PropertyInfo propertyInfo => propertyInfo.GetValue(obj), + FieldInfo fieldInfo => fieldInfo.GetValue(obj), + _ => throw new InvalidOperationException() + }; + } + + public static Type GetMemberType(this MemberInfo memberInfo) + { + return memberInfo switch + { + PropertyInfo propertyInfo => propertyInfo.PropertyType, + FieldInfo fieldInfo => fieldInfo.FieldType, + _ => throw new InvalidOperationException() + }; + } + } +} \ No newline at end of file diff --git a/ObjectPrinting/ObjectPrinting.csproj b/ObjectPrinting/ObjectPrinting.csproj index 1c5eaf1c..730c9586 100644 --- a/ObjectPrinting/ObjectPrinting.csproj +++ b/ObjectPrinting/ObjectPrinting.csproj @@ -1,14 +1,16 @@  - - 8 - netcoreapp3.1 - false - + + 9.0 + net8.0 + false + - - - - + + + + + + diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index a9e08211..ba561eba 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -1,41 +1,190 @@ using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Globalization; using System.Linq; +using System.Linq.Expressions; +using System.Reflection; using System.Text; namespace ObjectPrinting { public class PrintingConfig { + private static readonly HashSet _finalTypes = new() + { + typeof(int), typeof(uint), typeof(double), typeof(float), typeof(decimal), + typeof(long), typeof(ulong), typeof(short), typeof(ushort), + typeof(string), typeof(bool), + typeof(DateTime), typeof(TimeSpan), typeof(Guid) + }; + + private readonly HashSet excludedProperties = new(); + private readonly HashSet excludedTypes = new(); + protected readonly Dictionary propertiesMaxLength = new(); + protected readonly Dictionary> propertiesSerialization = new(); + protected readonly Dictionary typesCulture = new(); + protected readonly Dictionary> typesSerialization = new(); + private Func recursivePropertiesSerialization; + + public PrintingConfig() + { + } + + protected PrintingConfig(PrintingConfig config) + { + typesCulture = config.typesCulture; + propertiesMaxLength = config.propertiesMaxLength; + propertiesSerialization = config.propertiesSerialization; + typesSerialization = config.typesSerialization; + } + public string PrintToString(TOwner obj) { - return PrintToString(obj, 0); + return SerializeObject(obj, ImmutableList.Empty); + } + + public PrintingConfig Excluding() + { + excludedTypes.Add(typeof(TPropType)); + + return this; + } + + public PrintingConfig Excluding(Expression> member) + { + var memberInfo = GetMember(member); + + excludedProperties.Add(memberInfo); + + return this; + } + + public TypePrintingConfig Printing() + { + return new TypePrintingConfig(this); + } + + public PropertyPrintingConfig Printing( + Expression> printMember) + { + var memberInfo = GetMember(printMember); + + return new PropertyPrintingConfig(this, memberInfo); + } + + public PrintingConfig WithCyclicLinkException() + { + recursivePropertiesSerialization = () => throw new ArgumentException("recursive property"); + + return this; + } + + public PrintingConfig WithCyclicLinkMessage(Func printProperty) + { + recursivePropertiesSerialization = printProperty; + + return this; + } + + public PrintingConfig WithCyclicLinkIgnored() + { + recursivePropertiesSerialization = () => string.Empty; + + return this; } - private string PrintToString(object obj, int nestingLevel) + private string SerializeObject(object obj, IImmutableList previousObjects) { - //TODO apply configurations if (obj == null) - return "null" + Environment.NewLine; + return "null"; - var finalTypes = new[] + var type = obj.GetType(); + if (previousObjects.Any(prev => prev == obj)) { - 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(); + return recursivePropertiesSerialization != null + ? (string)recursivePropertiesSerialization() : "Recursive property"; + } + + if (obj is IEnumerable && obj is not string) + return SerializeEnumerable(obj, previousObjects); + + if (_finalTypes.Contains(type)) + return SerializeFinalType(obj); + + var indentation = new string('\t', previousObjects.Count + 1); + var objectSerialization = new StringBuilder().AppendLine(type.Name); + previousObjects = previousObjects.Add(obj); + var members = Array.Empty().Concat(type.GetProperties()).Concat(type.GetFields()); + foreach (var memberInfo in members) + { + if (excludedProperties.Contains(memberInfo) + || excludedTypes.Contains(memberInfo.GetMemberType())) + continue; + + objectSerialization.Append(indentation); + objectSerialization.Append(memberInfo.Name); + objectSerialization.Append(" = "); + objectSerialization.Append(SerializeMember(memberInfo, obj, previousObjects)); + objectSerialization.Append(Environment.NewLine); + } + + return objectSerialization.ToString(); + } + + private string SerializeFinalType(object obj) + { var type = obj.GetType(); - sb.AppendLine(type.Name); - foreach (var propertyInfo in type.GetProperties()) + if (typesSerialization.TryGetValue(type, out var serialization)) + return serialization.Invoke(obj); + + var result = obj.ToString(); + if (typesCulture.ContainsKey(obj.GetType())) + result = ((IFormattable)obj).ToString(null, typesCulture[type]); + + return result; + } + + private string SerializeMember(MemberInfo memberInfo, object obj, IImmutableList previousObjects) + { + var value = memberInfo.GetMemberValue(obj); + var result = propertiesSerialization.TryGetValue(memberInfo, out var serialization) + ? serialization.Invoke(value) + : SerializeObject(value, previousObjects).TrimEnd(); + + var propertyMaxLength = propertiesMaxLength.TryGetValue(memberInfo, out var length) + ? length + : result.Length; + + result = result[..propertyMaxLength]; + + return result; + } + + private string SerializeEnumerable(object obj, IImmutableList previousObjects) + { + var enumerable = (IEnumerable)obj; + var serializedEnumerable = new StringBuilder(); + var indentation = previousObjects.Count == 0 + ? string.Empty + : Environment.NewLine + new string('\t', previousObjects.Count + 1); + + foreach (var item in enumerable) { - sb.Append(identation + propertyInfo.Name + " = " + - PrintToString(propertyInfo.GetValue(obj), - nestingLevel + 1)); + serializedEnumerable.Append(indentation); + serializedEnumerable.Append(SerializeObject(item, previousObjects)); } - return sb.ToString(); + + return serializedEnumerable.ToString(); + } + + private static MemberInfo GetMember(Expression> expression) + { + var memberBody = (MemberExpression)expression.Body; + var memberInfo = memberBody.Member; + + return memberInfo; } } } \ No newline at end of file diff --git a/ObjectPrinting/PropertyPrintingConfig.cs b/ObjectPrinting/PropertyPrintingConfig.cs new file mode 100644 index 00000000..a137241e --- /dev/null +++ b/ObjectPrinting/PropertyPrintingConfig.cs @@ -0,0 +1,31 @@ +using System; +using System.Reflection; + +namespace ObjectPrinting +{ + public class PropertyPrintingConfig : PrintingConfig + { + private readonly MemberInfo propertyInfo; + + public PropertyPrintingConfig(PrintingConfig config, MemberInfo propertyInfo) : base(config) + { + this.propertyInfo = propertyInfo; + } + + public PropertyPrintingConfig Using(Func printProperty) + { + propertiesSerialization.TryAdd(propertyInfo, printProperty); + + return this; + } + + public PropertyPrintingConfig TrimToLength(int length) + { + ArgumentOutOfRangeException.ThrowIfNegative(length); + + propertiesMaxLength.Add(propertyInfo, length); + + return this; + } + } +} \ No newline at end of file diff --git a/ObjectPrinting/Solved/PrintingConfig.cs b/ObjectPrinting/Solved/PrintingConfig.cs index 0ec5aeb2..f52c669a 100644 --- a/ObjectPrinting/Solved/PrintingConfig.cs +++ b/ObjectPrinting/Solved/PrintingConfig.cs @@ -12,7 +12,8 @@ public PropertyPrintingConfig Printing() return new PropertyPrintingConfig(this); } - public PropertyPrintingConfig Printing(Expression> memberSelector) + public PropertyPrintingConfig Printing( + Expression> memberSelector) { return new PropertyPrintingConfig(this); } @@ -51,11 +52,9 @@ private string PrintToString(object obj, int nestingLevel) 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(); } } diff --git a/ObjectPrinting/Solved/PropertyPrintingConfig.cs b/ObjectPrinting/Solved/PropertyPrintingConfig.cs index a509697d..8cbfbf08 100644 --- a/ObjectPrinting/Solved/PropertyPrintingConfig.cs +++ b/ObjectPrinting/Solved/PropertyPrintingConfig.cs @@ -12,6 +12,8 @@ public PropertyPrintingConfig(PrintingConfig printingConfig) this.printingConfig = printingConfig; } + PrintingConfig IPropertyPrintingConfig.ParentConfig => printingConfig; + public PrintingConfig Using(Func print) { return printingConfig; @@ -21,8 +23,6 @@ public PrintingConfig Using(CultureInfo culture) { return printingConfig; } - - PrintingConfig IPropertyPrintingConfig.ParentConfig => printingConfig; } public interface IPropertyPrintingConfig diff --git a/ObjectPrinting/Solved/PropertyPrintingConfigExtensions.cs b/ObjectPrinting/Solved/PropertyPrintingConfigExtensions.cs index dd392239..61710607 100644 --- a/ObjectPrinting/Solved/PropertyPrintingConfigExtensions.cs +++ b/ObjectPrinting/Solved/PropertyPrintingConfigExtensions.cs @@ -9,10 +9,10 @@ public static string PrintToString(this T obj, Func, Printi return config(ObjectPrinter.For()).PrintToString(obj); } - public static PrintingConfig TrimmedToLength(this PropertyPrintingConfig propConfig, int maxLen) + 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 index ac52d5ee..9582fd84 100644 --- a/ObjectPrinting/Solved/Tests/ObjectPrinterAcceptanceTests.cs +++ b/ObjectPrinting/Solved/Tests/ObjectPrinterAcceptanceTests.cs @@ -25,13 +25,13 @@ public void Demo() //6. Исключить из сериализации конкретного свойства .Excluding(p => p.Age); - string s1 = printer.PrintToString(person); - + var s1 = printer.PrintToString(person); + //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию - string s2 = person.PrintToString(); - + var s2 = person.PrintToString(); + //8. ...с конфигурированием - string s3 = person.PrintToString(s => s.Excluding(p => p.Age)); + var s3 = person.PrintToString(s => s.Excluding(p => p.Age)); Console.WriteLine(s1); Console.WriteLine(s2); Console.WriteLine(s3); diff --git a/ObjectPrinting/Tests/File.cs b/ObjectPrinting/Tests/File.cs new file mode 100644 index 00000000..5caf44d0 --- /dev/null +++ b/ObjectPrinting/Tests/File.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; + +namespace ObjectPrinting.Tests +{ + public class File + { + public string field; + public string Name { get; set; } + + public Dictionary Attributes { get; set; } + + public List SimilarNames { get; set; } + + public string[] Copies { get; set; } + } +} \ No newline at end of file diff --git a/ObjectPrinting/Tests/LinkedNode.cs b/ObjectPrinting/Tests/LinkedNode.cs new file mode 100644 index 00000000..cc0b3588 --- /dev/null +++ b/ObjectPrinting/Tests/LinkedNode.cs @@ -0,0 +1,9 @@ +namespace ObjectPrinting.Tests +{ + public class LinkedNode + { + public T Value { get; set; } + + public LinkedNode Next { get; set; } + } +} \ No newline at end of file diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs index 4c8b2445..28f2a8ea 100644 --- a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs +++ b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs @@ -1,4 +1,6 @@ -using NUnit.Framework; +using System.Globalization; +using FluentAssertions; +using NUnit.Framework; namespace ObjectPrinting.Tests { @@ -6,22 +8,27 @@ namespace ObjectPrinting.Tests public class ObjectPrinterAcceptanceTests { [Test] - public void Demo() + public void AcceptanceTest() { - var person = new Person { Name = "Alex", Age = 19 }; + var person = new Person { Name = "Alex", Age = 19, Height = 1.2 }; - var printer = ObjectPrinter.For(); - //1. Исключить из сериализации свойства определенного типа - //2. Указать альтернативный способ сериализации для определенного типа - //3. Для числовых типов указать культуру - //4. Настроить сериализацию конкретного свойства - //5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств) - //6. Исключить из сериализации конкретного свойства - - string s1 = printer.PrintToString(person); + var printer = ObjectPrinter.For() + .Excluding() + .Printing() + .WithCulture(CultureInfo.CurrentCulture) + .Using(x => (x * 2).ToString()) + .Printing(person => person.Name) + .Using(name => $"{name}ing") + .TrimToLength(2) + .Excluding(person => person.Age) + .WithCyclicLinkMessage(() => "Recursion") + .WithCyclicLinkIgnored() + .WithCyclicLinkException(); - //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию - //8. ...с конфигурированием + var printedPerson = printer.PrintToString(person); + + printedPerson.Should().Be("Person\r\n\tId = 00000000-0000-0000-0000-000000000000\r\n\t" + + "Name = Al\r\n\tHeight = 2,4\r\n"); } } } \ No newline at end of file diff --git a/ObjectPrinting/Tests/ObjectPrinterTests.cs b/ObjectPrinting/Tests/ObjectPrinterTests.cs new file mode 100644 index 00000000..f681d747 --- /dev/null +++ b/ObjectPrinting/Tests/ObjectPrinterTests.cs @@ -0,0 +1,390 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using FluentAssertions; +using NUnit.Framework; + +namespace ObjectPrinting.Tests +{ + [TestFixture] + public class ObjectPrinterTests + { + [SetUp] + public void SetUp() + { + person = new Person { Name = "Alex", Age = 19, Height = 1.2 }; + } + + private Person person; + + [Test] + public void PrintToString_ShouldReturnStringWithEveryObjectProperty() + { + var printer = ObjectPrinter.For(); + + var personWithEveryProperty = printer.PrintToString(person); + + personWithEveryProperty + .Should() + .Be("Person\r\n\tId = 00000000-0000-0000-0000-000000000000\r\n\t" + + "Name = Alex\r\n\tHeight = 1,2\r\n\tAge = 19\r\n"); + } + + [Test] + public void PrintToString_ShouldExcludeProperty_WhenItsTypeExcluded() + { + var printer = ObjectPrinter.For().Excluding(); + + var personWithoutIntProperties = printer.PrintToString(person); + + personWithoutIntProperties.Should().Be("Person\r\n\tId = 00000000-0000-0000-0000-000000000000\r\n\t" + + "Name = Alex\r\n\tHeight = 1,2\r\n"); + } + + [Test] + public void PrintToString_ShouldExcludeProperty_WhenItsNameExcluded() + { + var printer = ObjectPrinter.For().Excluding(person => person.Name); + + var personWithoutNameProperty = printer.PrintToString(person); + + personWithoutNameProperty.Should().Be("Person\r\n\tId = 00000000-0000-0000-0000-000000000000\r\n\t" + + "Height = 1,2\r\n\tAge = 19\r\n"); + } + + [Test] + public void PrintToString_ShouldUseTypeCulture_WhenItsSpecified() + { + var culture = new CultureInfo("en-US"); + var printer = ObjectPrinter.For().Printing().WithCulture(culture); + + var personWithUSCultureForDouble = printer.PrintToString(person); + + personWithUSCultureForDouble + .Should() + .Be("Person\r\n\tId = 00000000-0000-0000-0000-000000000000\r\n\tName = Alex\r\n\t" + + "Height = 1.2\r\n\tAge = 19\r\n"); + } + + [Test] + public void PrintToString_ShouldReturnStringWithEveryListObject_WhenListPassed() + { + var persons = new List { new() { Age = 1, Height = 1.2 }, new() { Age = 5 } }; + var printer = ObjectPrinter.For>(); + + var listOfPersonsWithEveryItem = printer.PrintToString(persons); + + listOfPersonsWithEveryItem + .Should() + .Be( + "Person\r\n\tId = 00000000-0000-0000-0000-000000000000\r\n\tName = null\r\n\tHeight = 1,2\r\n\tAge = 1\r\n" + + "Person\r\n\tId = 00000000-0000-0000-0000-000000000000\r\n\tName = null\r\n\tHeight = 0\r\n\tAge = 5\r\n"); + } + + [Test] + public void PrintToString_ShouldReturnStringWithEveryArrayObject_WhenArrayPassed() + { + var persons = new[] { new Person { Age = 1, Height = 1.2 }, new Person { Age = 5 } }; + var printer = ObjectPrinter.For(); + + var arrayOfPersonsWithEveryItem = printer.PrintToString(persons); + + arrayOfPersonsWithEveryItem + .Should() + .Be( + "Person\r\n\tId = 00000000-0000-0000-0000-000000000000\r\n\tName = null\r\n\tHeight = 1,2\r\n\tAge = 1\r\n" + + "Person\r\n\tId = 00000000-0000-0000-0000-000000000000\r\n\tName = null\r\n\tHeight = 0\r\n\tAge = 5\r\n"); + } + + [Test] + public void PrintToString_ShouldReturnStringWithEveryDictionaryObject_WhenDictionaryPassed() + { + var printer = ObjectPrinter.For>(); + var dict = new Dictionary + { + { person, "22" }, + { new Person(), "12" } + }; + + var fileWithListProperty = printer.PrintToString(dict); + + fileWithListProperty + .Should() + .Be("KeyValuePair`2\r\n\tKey = Person\r\n\t\tId = 00000000-0000-0000-0000-000000000000\r\n\t\t" + + "Name = Alex\r\n\t\tHeight = 1,2\r\n\t\tAge = 19\r\n\tValue = 22\r\nKeyValuePair`2\r\n\t" + + "Key = Person\r\n\t\tId = 00000000-0000-0000-0000-000000000000\r\n\t\t" + + "Name = null\r\n\t\tHeight = 0\r\n\t\tAge = 0\r\n\tValue = 12\r\n"); + } + + [Test] + public void PrintToString_ShouldReturnStringWithCustomSerializationForType_WhenItsSpecified() + { + var printer = ObjectPrinter.For().Printing().Using(x => (x * 2).ToString()); + + var personWithEveryIntPropertyMultByTwo = printer.PrintToString(person); + + personWithEveryIntPropertyMultByTwo + .Should() + .Be("Person\r\n\tId = 00000000-0000-0000-0000-000000000000\r\n\t" + + "Name = Alex\r\n\tHeight = 1,2\r\n\tAge = 38\r\n"); + } + + [Test] + public void + PrintToString_ShouldReturnStringWithCustomSerializationProperty_WhenItsSpecified() + { + var printer = ObjectPrinter + .For() + .Printing(person => person.Name) + .Using(name => $"My name is {name}"); + + var personWithCustomNamePropertySerializer = printer.PrintToString(person); + + personWithCustomNamePropertySerializer + .Should() + .Be("Person\r\n\tId = 00000000-0000-0000-0000-000000000000\r\n\tName = My name is Alex\r\n\t" + + "Height = 1,2\r\n\tAge = 19\r\n"); + } + + [Test] + public void PrintToString_ShouldReturnStringWithCustomLength_WhenItsSpecified() + { + var printer = ObjectPrinter.For() + .Printing(person => person.Name) + .TrimToLength(2); + + var personWithTrimmedNameProperty = printer.PrintToString(person); + + personWithTrimmedNameProperty + .Should() + .Be( + "Person\r\n\tId = 00000000-0000-0000-0000-000000000000\r\n\tName = Al\r\n\tHeight = 1,2\r\n\tAge = 19\r\n"); + } + + [Test] + public void PrintToString_ShouldReturnStringWithoutRecursion_WhenObjectRefersOnItself() + { + var student = new Student { Name = "Alex", Age = 10 }; + var anotherStudent = new Student { Teacher = student, Name = "Vovchik", Age = 15 }; + student.Teacher = anotherStudent; + var printer = ObjectPrinter.For(); + + var studentWithTeacherRefOnItself = printer.PrintToString(student); + + studentWithTeacherRefOnItself + .Should() + .Be("Student\r\n\tTeacher = Student\r\n\t\tTeacher = Recursive property\r\n\t\t" + + "Friend = null\r\n\t\tId = 00000000-0000-0000-0000-000000000000\r\n\t\tName = Vovchik\r\n\t\t" + + "Height = 0\r\n\t\tAge = 15\r\n\tFriend = null\r\n\tId = 00000000-0000-0000-0000-000000000000\r\n\t" + + "Name = Alex\r\n\tHeight = 0\r\n\tAge = 10\r\n"); + } + + [Test] + public void PrintToString_ShouldReturnStringWithoutRecursion_WhenLinkedListRecured() + { + var firstLinkedNode = new LinkedNode(); + var secondLinkedNode = new LinkedNode(); + var thirdLinkedNode = new LinkedNode(); + firstLinkedNode.Next = secondLinkedNode; + secondLinkedNode.Next = thirdLinkedNode; + thirdLinkedNode.Next = firstLinkedNode; + var printer = ObjectPrinter.For>(); + + var printedList = printer.PrintToString(firstLinkedNode); + + printedList.Should().Be("LinkedNode`1\r\n\tValue = 0\r\n\tNext = LinkedNode`1\r\n\t\tValue = 0\r\n\t\t" + + "Next = LinkedNode`1\r\n\t\t\tValue = 0\r\n\t\t\tNext = Recursive property\r\n"); + } + + [Test] + public void PrintToString_ShouldReturnStringWithCustomRecursionSerialization_WhenItsSpecified() + { + var student = new Student { Name = "Alex", Age = 10 }; + var anotherStudent = new Student { Teacher = student, Name = "Vovchik", Age = 15 }; + student.Teacher = anotherStudent; + var printer = ObjectPrinter.For().WithCyclicLinkMessage(() => "Recursion"); + + var studentWithTeacherRefOnItself = printer.PrintToString(student); + + studentWithTeacherRefOnItself + .Should() + .Be("Student\r\n\tTeacher = Student\r\n\t\tTeacher = Recursion\r\n\t\t" + + "Friend = null\r\n\t\tId = 00000000-0000-0000-0000-000000000000\r\n\t\tName = Vovchik\r\n\t\t" + + "Height = 0\r\n\t\tAge = 15\r\n\tFriend = null\r\n\tId = 00000000-0000-0000-0000-000000000000\r\n\t" + + "Name = Alex\r\n\tHeight = 0\r\n\tAge = 10\r\n"); + } + + [Test] + public void PrintToString_ShouldReturnStringWithIgnoredRecuresdProperty_WhenItsSpecified() + { + var student = new Student { Name = "Alex", Age = 10 }; + var anotherStudent = new Student { Teacher = student, Name = "Vovchik", Age = 15 }; + student.Teacher = anotherStudent; + var printer = ObjectPrinter.For().WithCyclicLinkIgnored(); + + var studentWithTeacherRefOnItself = printer.PrintToString(student); + + studentWithTeacherRefOnItself + .Should() + .Be("Student\r\n\tTeacher = Student\r\n\t\tTeacher = \r\n\t\t" + + "Friend = null\r\n\t\tId = 00000000-0000-0000-0000-000000000000\r\n\t\tName = Vovchik\r\n\t\t" + + "Height = 0\r\n\t\tAge = 15\r\n\tFriend = null\r\n\tId = 00000000-0000-0000-0000-000000000000\r\n\t" + + "Name = Alex\r\n\tHeight = 0\r\n\tAge = 10\r\n"); + } + + [Test] + public void PrintToString_ShouldThrowException_WhenPropertyRecursed() + { + var student = new Student { Name = "Alex", Age = 10 }; + var anotherStudent = new Student { Teacher = student, Name = "Vovchik", Age = 15 }; + student.Teacher = anotherStudent; + var printer = ObjectPrinter.For().WithCyclicLinkException(); + + Assert.Throws(() => printer.PrintToString(student)); + } + + [Test] + public void PrintToString_ShouldPrintAllObjects_WhenTheyHaveConnectedRefs() + { + var firstStudent = new Student { Name = "Alex", Age = 10 }; + var secondStudent = new Student { Name = "Miha", Age = 15 }; + var thirdstudent = new Student { Name = "Petr", Age = 12 }; + firstStudent.Teacher = thirdstudent; + firstStudent.Friend = secondStudent; + secondStudent.Teacher = thirdstudent; + var printer = ObjectPrinter.For(); + + var notRecursedStudents = printer.PrintToString(firstStudent); + + notRecursedStudents.Should() + .Be("Student\r\n\tTeacher = Student\r\n\t\tTeacher = null\r\n\t\t" + + "Friend = null\r\n\t\tId = 00000000-0000-0000-0000-000000000000\r\n\t\tName = Petr\r\n\t\t" + + "Height = 0\r\n\t\tAge = 12\r\n\tFriend = Student\r\n\t\tTeacher = Student\r\n\t\t\t" + + "Teacher = null\r\n\t\t\tFriend = null\r\n\t\t\tId = 00000000-0000-0000-0000-000000000000\r\n\t\t\t" + + "Name = Petr\r\n\t\t\tHeight = 0\r\n\t\t\tAge = 12\r\n\t\tFriend = null\r\n\t\t" + + "Id = 00000000-0000-0000-0000-000000000000\r\n\t\tName = Miha\r\n\t\tHeight = 0\r\n\t\tAge = 15\r\n\t" + + "Id = 00000000-0000-0000-0000-000000000000\r\n\tName = Alex\r\n\tHeight = 0\r\n\tAge = 10\r\n"); + } + + [Test] + public void PrintToString_ShouldReturnStringObjectWithSerializedDictionary_WhenObjectHasDictionary() + { + var printer = ObjectPrinter.For(); + var file = new File { Name = "file", Attributes = new Dictionary { { "a", "b" } } }; + + var fileWithDictionaryProperty = printer.PrintToString(file); + + fileWithDictionaryProperty + .Should() + .Be("File\r\n\tName = file\r\n\tAttributes = \r\n\t\tKeyValuePair`2\r\n\t\tKey = a\r\n\t\t" + + "Value = b\r\n\tSimilarNames = null\r\n\tCopies = null\r\n\tfield = null\r\n"); + } + + [Test] + public void PrintToString_ShouldReturnStringObjectWithSerializedList_WhenObjectHasList() + { + var printer = ObjectPrinter.For(); + var file = new File + { + Name = "file", + SimilarNames = new List { "oleg.jpg", "oleg.png" } + }; + + var fileWithListProperty = printer.PrintToString(file); + + fileWithListProperty + .Should() + .Be("File\r\n\tName = file\r\n\tAttributes = null\r\n\tSimilarNames = \r\n\t\toleg.jpg\r\n\t\t" + + "oleg.png\r\n\tCopies = null\r\n\tfield = null\r\n"); + } + + [Test] + public void PrintToString_ShouldReturnStringObjectWithSerializedArray_WhenObjectHasArray() + { + var printer = ObjectPrinter.For(); + var file = new File + { + Name = "file", + Copies = new[] { "oleg.jpg", "oleg.png" } + }; + + var fileWithListProperty = printer.PrintToString(file); + + fileWithListProperty + .Should() + .Be("File\r\n\tName = file\r\n\tAttributes = null\r\n\tSimilarNames = null\r\n\tCopies = \r\n\t\t" + + "oleg.jpg\r\n\t\toleg.png\r\n\tfield = null\r\n"); + } + + [Test] + public void PrintToString_ShouldPrintListWithNullValue_WhenObjectHasEmptyList() + { + var printer = ObjectPrinter.For(); + var file = new File + { + Name = "file" + }; + + var fileWithListProperty = printer.PrintToString(file); + + fileWithListProperty + .Should() + .Be("File\r\n\tName = file\r\n\tAttributes = null\r\n\tSimilarNames = null\r\n\t" + + "Copies = null\r\n\tfield = null\r\n"); + } + + [Test] + public void PrintToString_ShouldPrintArrayWithNullValue_WhenObjectHasEmptyArray() + { + var printer = ObjectPrinter.For(); + var file = new File + { + Name = "file" + }; + + var fileWithListProperty = printer.PrintToString(file); + + fileWithListProperty + .Should() + .Be("File\r\n\tName = file\r\n\tAttributes = null\r\n\tSimilarNames = null\r\n\t" + + "Copies = null\r\n\tfield = null\r\n"); + } + + [Test] + public void PrintToString_ShouldPrintDictionaryWithNullValue_WhenObjectHasEmptyDictionary() + { + var printer = ObjectPrinter.For(); + var file = new File + { + Name = "file" + }; + + var fileWithListProperty = printer.PrintToString(file); + + fileWithListProperty + .Should() + .Be("File\r\n\tName = file\r\n\tAttributes = null\r\n\tSimilarNames = null\r\n\t" + + "Copies = null\r\n\tfield = null\r\n"); + } + + [Test] + public void TrimToLength_ShouldThrowArgumentOutOfRangeException_WhenPassedNegativeLength() + { + var printer = ObjectPrinter.For() + .Printing(person => person.Name); + + + Assert.Throws(() => printer.TrimToLength(-1)); + } + + [Test] + public void PrintToString_ShouldThrowArgumentOutOfRangeException_WhenTrimToLengthBiggerThanPropertyLength() + { + var printer = ObjectPrinter.For() + .Printing(person => person.Name).TrimToLength(20); + + + Assert.Throws(() => printer.PrintToString(person)); + } + } +} \ No newline at end of file diff --git a/ObjectPrinting/Tests/Person.cs b/ObjectPrinting/Tests/Person.cs index f9555955..9cee17de 100644 --- a/ObjectPrinting/Tests/Person.cs +++ b/ObjectPrinting/Tests/Person.cs @@ -5,8 +5,11 @@ namespace ObjectPrinting.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/Student.cs b/ObjectPrinting/Tests/Student.cs new file mode 100644 index 00000000..6ab9d89e --- /dev/null +++ b/ObjectPrinting/Tests/Student.cs @@ -0,0 +1,9 @@ +namespace ObjectPrinting.Tests +{ + public class Student : Person + { + public Person Teacher { get; set; } + + public Person Friend { get; set; } + } +} \ No newline at end of file diff --git a/ObjectPrinting/TypePrintingConfig.cs b/ObjectPrinting/TypePrintingConfig.cs new file mode 100644 index 00000000..6396c9af --- /dev/null +++ b/ObjectPrinting/TypePrintingConfig.cs @@ -0,0 +1,26 @@ +using System; +using System.Globalization; + +namespace ObjectPrinting +{ + public class TypePrintingConfig : PrintingConfig + { + public TypePrintingConfig(PrintingConfig config) : base(config) + { + } + + public TypePrintingConfig WithCulture(CultureInfo cultureInfo) where T : IFormattable + { + typesCulture.Add(typeof(T), cultureInfo); + + return this; + } + + public TypePrintingConfig Using(Func printType) + { + typesSerialization.TryAdd(typeof(TPropType), printType); + + return this; + } + } +} \ No newline at end of file