From e390215d5de94f26010eaf8bf7aa637a08fae056 Mon Sep 17 00:00:00 2001 From: RenatZubakin Date: Sun, 24 Dec 2023 20:43:28 +0500 Subject: [PATCH 1/6] Add Exclude by type and Property and tests --- ObjectPrinting/ObjectPrinting.csproj | 1 + ObjectPrinting/PrintingConfig.cs | 51 ++++++++++++---- .../Tests/ObjectPrinterAcceptanceTests.cs | 61 ++++++++++++++++--- ObjectPrinting/Tests/Person.cs | 1 + 4 files changed, 93 insertions(+), 21 deletions(-) diff --git a/ObjectPrinting/ObjectPrinting.csproj b/ObjectPrinting/ObjectPrinting.csproj index 1c5eaf1c..4f9c587e 100644 --- a/ObjectPrinting/ObjectPrinting.csproj +++ b/ObjectPrinting/ObjectPrinting.csproj @@ -7,6 +7,7 @@ + diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index a9e08211..8ef5c5ec 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -1,11 +1,27 @@ using System; +using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; +using System.Reflection; using System.Text; namespace ObjectPrinting { public class PrintingConfig { + private static readonly HashSet FinalTypes = new HashSet() + { + typeof(int), + typeof(double), + typeof(float), + typeof(string), + typeof(DateTime), + typeof(TimeSpan), + }; + + private readonly HashSet excludedTypes = new HashSet(); + private readonly HashSet excludedProperty = new HashSet(); + public string PrintToString(TOwner obj) { return PrintToString(obj, 0); @@ -15,27 +31,40 @@ private string PrintToString(object obj, int nestingLevel) { //TODO apply configurations if (obj == null) - return "null" + Environment.NewLine; + return "null"; + + if (FinalTypes.Contains(obj.GetType())) + return obj.ToString(); - 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 identation = new string(" "); var sb = new StringBuilder(); var type = obj.GetType(); - sb.AppendLine(type.Name); + sb.Append(type.Name); foreach (var propertyInfo in type.GetProperties()) { + if (excludedTypes.Contains(propertyInfo.PropertyType) || excludedProperty.Contains(propertyInfo)) + continue; sb.Append(identation + propertyInfo.Name + " = " + PrintToString(propertyInfo.GetValue(obj), nestingLevel + 1)); } + return sb.ToString(); } + + public PrintingConfig Exclude() + { + excludedTypes.Add(typeof(TType)); + return this; + } + + public PrintingConfig Exclude(Expression> func) + { + var memberInfo = func.Body as MemberExpression; + if (memberInfo == null) + throw new ArgumentException(); + excludedProperty.Add(memberInfo.Member); + return this; + } } } \ No newline at end of file diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs index 4c8b2445..53918f06 100644 --- a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs +++ b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs @@ -1,27 +1,68 @@ -using NUnit.Framework; +using System; +using FluentAssertions; +using NUnit.Framework; namespace ObjectPrinting.Tests { [TestFixture] public class ObjectPrinterAcceptanceTests { + private Person simplePerson; + private Person personWithParents; + private Person personWithCyclingReference; + private static readonly string NextProperty = $"\n\t"; + + [OneTimeSetUp] + public void OneTimeSetUp() + { + simplePerson = new Person { Id = new Guid("0f8fad5b-d9cb-469f-a165-70867728950e"),Name = "simplePerson", Age = 45, Height = 180 }; + personWithParents = new Person() { Id=new Guid("7c9e6679-7425-40de-944b-e07fc1f90ae7"),Name = "personWithParents", Age = 20, Height = 175, Parents = new Person[]{simplePerson}}; + personWithCyclingReference = new Person { Id=new Guid("b1754c14-d296-4b0f-a09a-030017f4461f"),Name = "Cycle", Age = 34, Height = 192 }; + personWithCyclingReference.Parents = new Person[] { personWithCyclingReference }; + } + [Test] public void Demo() { var person = new Person { Name = "Alex", Age = 19 }; var printer = ObjectPrinter.For(); - //1. Исключить из сериализации свойства определенного типа - //2. Указать альтернативный способ сериализации для определенного типа - //3. Для числовых типов указать культуру - //4. Настроить сериализацию конкретного свойства - //5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств) - //6. Исключить из сериализации конкретного свойства - string s1 = printer.PrintToString(person); + } + + [Test] + public void ObjectPrinter_Should_Serialize_All_Properties_When_Setting_Are_Default() + { + var printer = ObjectPrinter.For(); + var actual = printer.PrintToString(simplePerson); + + actual.Should().Be($"Person Id = Guid Name = simplePerson Height = 180 Age = 45 Parents = null"); + } - //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию - //8. ...с конфигурированием + [Test] + public void ObjectPrinter_Should_Excluding_PropertyType() + { + var printer = ObjectPrinter.For(); + var actual = printer.Exclude().PrintToString(simplePerson); + actual.Should().Be("Person Id = Guid Name = simplePerson Height = 180 Parents = null"); + } + + [Test] + public void ObjectPrinter_Should_Excluding_Property() + { + var printer = ObjectPrinter.For(); + var actual = printer.Exclude(person=>person.Name).PrintToString(simplePerson); + actual.Should().Be("Person Id = Guid Height = 180 Age = 45 Parents = null"); } + + + //1. Исключить из сериализации свойства определенного типа + //2. Указать альтернативный способ сериализации для определенного типа + //3. Для числовых типов указать культуру + //4. Настроить сериализацию конкретного свойства + //5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств) + //6. Исключить из сериализации конкретного свойства + //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию + //8. ...с конфигурированием } } \ No newline at end of file diff --git a/ObjectPrinting/Tests/Person.cs b/ObjectPrinting/Tests/Person.cs index f9555955..fb14f232 100644 --- a/ObjectPrinting/Tests/Person.cs +++ b/ObjectPrinting/Tests/Person.cs @@ -8,5 +8,6 @@ public class Person public string Name { get; set; } public double Height { get; set; } public int Age { get; set; } + public Person[] Parents { get; set; } } } \ No newline at end of file From 28ca9f5646422e53ca9bfb626ac5aaee9223575c Mon Sep 17 00:00:00 2001 From: RenatZubakin Date: Sun, 24 Dec 2023 22:52:07 +0500 Subject: [PATCH 2/6] Add objects printing extension --- ObjectPrinting/Extensions/ObjectExtensions.cs | 17 +++++++++ ObjectPrinting/PrintingConfig.cs | 35 ++++++++++++++++--- .../Tests/ObjectPrinterAcceptanceTests.cs | 28 +++++++++++---- 3 files changed, 68 insertions(+), 12 deletions(-) create mode 100644 ObjectPrinting/Extensions/ObjectExtensions.cs diff --git a/ObjectPrinting/Extensions/ObjectExtensions.cs b/ObjectPrinting/Extensions/ObjectExtensions.cs new file mode 100644 index 00000000..495565f7 --- /dev/null +++ b/ObjectPrinting/Extensions/ObjectExtensions.cs @@ -0,0 +1,17 @@ +using System; + +namespace ObjectPrinting.Extensions +{ + public static class ObjectExtensions + { + public static string PrintToString(this T obj) + { + return ObjectPrinter.For().PrintToString(obj); + } + + public static string PrintToString(this T obj, Func,PrintingConfig> func) + { + return func(ObjectPrinter.For()).PrintToString(obj); + } + } +} \ No newline at end of file diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index 8ef5c5ec..b6759d12 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -11,17 +11,22 @@ public class PrintingConfig { private static readonly HashSet FinalTypes = new HashSet() { - typeof(int), - typeof(double), - typeof(float), + typeof(int), + typeof(double), + typeof(float), typeof(string), - typeof(DateTime), + typeof(DateTime), typeof(TimeSpan), }; private readonly HashSet excludedTypes = new HashSet(); private readonly HashSet excludedProperty = new HashSet(); + private readonly Dictionary customPropertySerialize = + new Dictionary(); + + private readonly Dictionary customTypeSerializers = new Dictionary(); + public string PrintToString(TOwner obj) { return PrintToString(obj, 0); @@ -32,7 +37,11 @@ private string PrintToString(object obj, int nestingLevel) //TODO apply configurations if (obj == null) return "null"; - + + if (customTypeSerializers.ContainsKey(obj.GetType())) + { + return (string)customTypeSerializers[obj.GetType()].DynamicInvoke(obj); + } if (FinalTypes.Contains(obj.GetType())) return obj.ToString(); @@ -66,5 +75,21 @@ public PrintingConfig Exclude(Expression SetCustomTypeSerializer(Func serializer) + { + customTypeSerializers[typeof(TType)] = serializer; + return this; + } + + public PrintingConfig SetCustomPropertySerializer(Expression>> serializer) + { + var serializerInfo = serializer.Parameters[1] as Expression>; + var Tpropertyinfo = serializerInfo.Parameters[1]; + + return this; + } + } } \ No newline at end of file diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs index 53918f06..5a77ba2e 100644 --- a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs +++ b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs @@ -1,6 +1,7 @@ using System; using FluentAssertions; using NUnit.Framework; +using ObjectPrinting.Extensions; namespace ObjectPrinting.Tests { @@ -51,18 +52,31 @@ public void ObjectPrinter_Should_Excluding_PropertyType() public void ObjectPrinter_Should_Excluding_Property() { var printer = ObjectPrinter.For(); - var actual = printer.Exclude(person=>person.Name).PrintToString(simplePerson); + var actual = printer.Exclude(person=>person.Name).PrintToString(simplePerson); actual.Should().Be("Person Id = Guid Height = 180 Age = 45 Parents = null"); } + [Test] + public void ObjectPrinter_Should_Serialize_By_Type() + { + var printer = ObjectPrinter.For(); + var actual = printer.SetCustomTypeSerializer(i=>0.ToString()).PrintToString(simplePerson); + actual.Should().Be(""); + } + + public void ObjectPrinter_Shoult_Serialize_By_Property() + { + var printer = ObjectPrinter.For(); + } + - //1. Исключить из сериализации свойства определенного типа - //2. Указать альтернативный способ сериализации для определенного типа + //1. Исключить из сериализации свойства определенного типа + + //2. Указать альтернативный способ сериализации для определенного типа+ //3. Для числовых типов указать культуру - //4. Настроить сериализацию конкретного свойства + //4. Настроить сериализацию конкретного свойства //5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств) - //6. Исключить из сериализации конкретного свойства - //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию - //8. ...с конфигурированием + //6. Исключить из сериализации конкретного свойства + //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию + + //8. ...с конфигурированием + } } \ No newline at end of file From 5f2244941a9a51f4aa8a6d0f9dace336cd763acf Mon Sep 17 00:00:00 2001 From: RenatZubakin Date: Mon, 25 Dec 2023 23:00:00 +0500 Subject: [PATCH 3/6] All tasks done --- .../PropertyPrintingConfigExtension.cs | 11 ++ ObjectPrinting/PrintingConfig.cs | 132 +++++++++++++++--- ObjectPrinting/PropertyPrintingConfig.cs | 23 +++ .../Tests/ObjectPrinterAcceptanceTests.cs | 128 +++++++++++++---- 4 files changed, 244 insertions(+), 50 deletions(-) create mode 100644 ObjectPrinting/Extensions/PropertyPrintingConfigExtension.cs create mode 100644 ObjectPrinting/PropertyPrintingConfig.cs diff --git a/ObjectPrinting/Extensions/PropertyPrintingConfigExtension.cs b/ObjectPrinting/Extensions/PropertyPrintingConfigExtension.cs new file mode 100644 index 00000000..6128db34 --- /dev/null +++ b/ObjectPrinting/Extensions/PropertyPrintingConfigExtension.cs @@ -0,0 +1,11 @@ +namespace ObjectPrinting.Extensions +{ + public static class PropertyPrintingConfigExtension + { + public static PrintingConfig TrimmedStringProperty( + this PropertyPrintingConfig printingConfig, int length) + { + return printingConfig.SetSerializer(str => str.Length <= length ? str : str.Substring(0, length)); + } + } +} \ No newline at end of file diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index b6759d12..520d0b9a 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -1,5 +1,7 @@ using System; +using System.Collections; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -17,6 +19,7 @@ public class PrintingConfig typeof(string), typeof(DateTime), typeof(TimeSpan), + typeof(Guid) }; private readonly HashSet excludedTypes = new HashSet(); @@ -26,6 +29,8 @@ public class PrintingConfig new Dictionary(); private readonly Dictionary customTypeSerializers = new Dictionary(); + private HashSet visitedObjects = new HashSet(); + public string PrintToString(TOwner obj) { @@ -34,31 +39,108 @@ public string PrintToString(TOwner obj) private string PrintToString(object obj, int nestingLevel) { + var builder = new StringBuilder(); //TODO apply configurations if (obj == null) return "null"; - if (customTypeSerializers.ContainsKey(obj.GetType())) + if (visitedObjects.Contains(obj)) { - return (string)customTypeSerializers[obj.GetType()].DynamicInvoke(obj); + return $"Cycling references"; } + + visitedObjects.Add(obj); + if (FinalTypes.Contains(obj.GetType())) return obj.ToString(); - var identation = new string(" "); - var sb = new StringBuilder(); - var type = obj.GetType(); - sb.Append(type.Name); - foreach (var propertyInfo in type.GetProperties()) + if (obj is IDictionary) + { + return PrintToStringIDictionary((IDictionary)obj, nestingLevel + 1); + } + + if (obj is IEnumerable) + { + return PrintToStringIEnumerable((IEnumerable)obj, nestingLevel + 1); + } + + return PrintToStringProperties(obj, nestingLevel + 1); + } + + private string PrintToStringProperties(object obj, int nestingLevel) + { + var identation = new string('\t', nestingLevel); + var builder = new StringBuilder(); + var objType = obj.GetType(); + builder.Append($"{objType.Name}"); + foreach (var propertyInfo in GetIncludedProperties(objType)) + { + var value = propertyInfo.GetValue(obj); + var customSerializeStr = TryUseCustomSerializer(value, propertyInfo, out var str) + ? str + : PrintToString(value, nestingLevel + 1); + builder.Append('\n' + identation + propertyInfo.Name + " = " + customSerializeStr); + } + + visitedObjects.Remove(obj); + return builder.ToString(); + } + + private string PrintToStringIEnumerable(IEnumerable collection, int nestingLevel) + { + var identation = new string('\t', nestingLevel); + var builder = new StringBuilder(); + + foreach (var item in collection) + { + builder.Append('\n' + identation + PrintToString(item, nestingLevel + 1)); + } + + visitedObjects.Remove(collection); + return builder.ToString(); + } + + private string PrintToStringIDictionary(IDictionary dictionary, int nestingLevel) + { + var identation = new string('\t', nestingLevel); + var builder = new StringBuilder(); + + foreach (var key in dictionary.Keys) { - if (excludedTypes.Contains(propertyInfo.PropertyType) || excludedProperty.Contains(propertyInfo)) - continue; - sb.Append(identation + propertyInfo.Name + " = " + - PrintToString(propertyInfo.GetValue(obj), - nestingLevel + 1)); + builder.Append('\n' + identation + PrintToString(key, nestingLevel + 1) + " = " + + PrintToString(dictionary[key], nestingLevel + 1)); } - return sb.ToString(); + visitedObjects.Remove(dictionary); + return builder.ToString(); + } + + private bool TryUseCustomSerializer(object value, MemberInfo memberInfo, out string str) + { + str = null; + if (customPropertySerialize.ContainsKey(memberInfo)) + { + str = (string)customPropertySerialize[memberInfo].DynamicInvoke(value); + return true; + } + + if (customTypeSerializers.ContainsKey(memberInfo.GetType())) + { + str = (string)customTypeSerializers[memberInfo.GetType()].DynamicInvoke(value); + return true; + } + + return false; + } + + private IEnumerable GetIncludedProperties(Type type) + { + return type.GetRuntimeProperties().Where(p => !IsExclude(p)); + } + + private bool IsExclude(MemberInfo member) + { + return excludedTypes.Contains(member.GetType()) || excludedProperty.Contains(member); } public PrintingConfig Exclude() @@ -82,14 +164,26 @@ public PrintingConfig SetCustomTypeSerializer(Func customTypeSerializers[typeof(TType)] = serializer; return this; } - - public PrintingConfig SetCustomPropertySerializer(Expression>> serializer) + + public PropertyPrintingConfig SerializeByProperty( + Expression> func) + { + var memberInfo = func.Body as MemberExpression; + if (memberInfo == null) + throw new ArgumentException(); + return new PropertyPrintingConfig(this, memberInfo.Member); + } + + public PrintingConfig SetCustomPropertySerializer(MemberInfo property, + Func serializer) { - var serializerInfo = serializer.Parameters[1] as Expression>; - var Tpropertyinfo = serializerInfo.Parameters[1]; - + customPropertySerialize[property] = serializer; return this; } - + + public PrintingConfig SetCulture(string format, CultureInfo culture) where TType : IFormattable + { + return SetCustomTypeSerializer((TType obj) => obj.ToString(format, culture)); + } } } \ No newline at end of file diff --git a/ObjectPrinting/PropertyPrintingConfig.cs b/ObjectPrinting/PropertyPrintingConfig.cs new file mode 100644 index 00000000..84fca04e --- /dev/null +++ b/ObjectPrinting/PropertyPrintingConfig.cs @@ -0,0 +1,23 @@ +using System; +using System.Reflection; + +namespace ObjectPrinting +{ + public class PropertyPrintingConfig + { + private readonly PrintingConfig printingConfig; + private readonly MemberInfo property; + + public PropertyPrintingConfig(PrintingConfig printingConfig, MemberInfo property) + { + this.printingConfig = printingConfig; + this.property = property; + } + + public PrintingConfig SetSerializer(Func serializer) + { + printingConfig.SetCustomPropertySerializer(property, serializer); + return printingConfig; + } + } +} \ No newline at end of file diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs index 5a77ba2e..04ae3774 100644 --- a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs +++ b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Globalization; using FluentAssertions; using NUnit.Framework; using ObjectPrinting.Extensions; @@ -9,35 +11,28 @@ namespace ObjectPrinting.Tests public class ObjectPrinterAcceptanceTests { private Person simplePerson; - private Person personWithParents; private Person personWithCyclingReference; - private static readonly string NextProperty = $"\n\t"; [OneTimeSetUp] public void OneTimeSetUp() { - simplePerson = new Person { Id = new Guid("0f8fad5b-d9cb-469f-a165-70867728950e"),Name = "simplePerson", Age = 45, Height = 180 }; - personWithParents = new Person() { Id=new Guid("7c9e6679-7425-40de-944b-e07fc1f90ae7"),Name = "personWithParents", Age = 20, Height = 175, Parents = new Person[]{simplePerson}}; - personWithCyclingReference = new Person { Id=new Guid("b1754c14-d296-4b0f-a09a-030017f4461f"),Name = "Cycle", Age = 34, Height = 192 }; + simplePerson = new Person + { + Id = new Guid("0f8fad5b-d9cb-469f-a165-70867728950e"), Name = "simplePerson", Age = 45, Height = 180 + }; + personWithCyclingReference = new Person + { Id = new Guid("b1754c14-d296-4b0f-a09a-030017f4461f"), Name = "Cycle", Age = 34, Height = 192 }; personWithCyclingReference.Parents = new Person[] { personWithCyclingReference }; } - [Test] - public void Demo() - { - var person = new Person { Name = "Alex", Age = 19 }; - - var printer = ObjectPrinter.For(); - string s1 = printer.PrintToString(person); - } - [Test] public void ObjectPrinter_Should_Serialize_All_Properties_When_Setting_Are_Default() { var printer = ObjectPrinter.For(); var actual = printer.PrintToString(simplePerson); - - actual.Should().Be($"Person Id = Guid Name = simplePerson Height = 180 Age = 45 Parents = null"); + actual.Should() + .Be( + $"Person\n\tId = 0f8fad5b-d9cb-469f-a165-70867728950e\n\tName = simplePerson\n\tHeight = 180\n\tAge = 45\n\tParents = null"); } [Test] @@ -45,38 +40,109 @@ public void ObjectPrinter_Should_Excluding_PropertyType() { var printer = ObjectPrinter.For(); var actual = printer.Exclude().PrintToString(simplePerson); - actual.Should().Be("Person Id = Guid Name = simplePerson Height = 180 Parents = null"); + actual.Should() + .Be( + "Person\n\tId = 0f8fad5b-d9cb-469f-a165-70867728950e\n\tName = simplePerson\n\tHeight = 180\n\tParents = null"); } - + [Test] public void ObjectPrinter_Should_Excluding_Property() { var printer = ObjectPrinter.For(); - var actual = printer.Exclude(person=>person.Name).PrintToString(simplePerson); - actual.Should().Be("Person Id = Guid Height = 180 Age = 45 Parents = null"); + var actual = printer.Exclude(person => person.Name).PrintToString(simplePerson); + actual.Should() + .Be( + "Person\n\tId = 0f8fad5b-d9cb-469f-a165-70867728950e\n\tHeight = 180\n\tAge = 45\n\tParents = null"); } [Test] - public void ObjectPrinter_Should_Serialize_By_Type() + public void ObjectPrinter_Should_Set_Custom_Serialize_For_Type() { var printer = ObjectPrinter.For(); - var actual = printer.SetCustomTypeSerializer(i=>0.ToString()).PrintToString(simplePerson); + var actual = printer.SetCustomTypeSerializer(i => 0.ToString()).PrintToString(simplePerson); actual.Should().Be(""); } - public void ObjectPrinter_Shoult_Serialize_By_Property() + [Test] + public void ObjectPrinter_Should_Set_Custom_Serializer_For_Property() + { + var printer = ObjectPrinter.For(); + var actual = printer.SerializeByProperty(person => person.Height).SetSerializer(height => height * 10 + "") + .PrintToString(simplePerson); + actual.Should() + .Be( + "Person\n\tId = 0f8fad5b-d9cb-469f-a165-70867728950e\n\tName = simplePerson\n\tHeight = 1800\n\tAge = 45\n\tParents = null"); + } + + [Test] + public void ObjectPrinter_Should_Trimm_String_Properties() { var printer = ObjectPrinter.For(); + var actual = printer.SerializeByProperty(person => person.Name).TrimmedStringProperty(2) + .PrintToString(simplePerson); + actual.Should() + .Be( + "Person\n\tId = 0f8fad5b-d9cb-469f-a165-70867728950e\n\tName = si\n\tHeight = 180\n\tAge = 45\n\tParents = null"); } + [Test] + public void ObjectPrinter_Should_Set_Culture_For_IFormatter_Types() + { + var d = 123456789.12345; + var printer = ObjectPrinter.For(); + var actual = printer.SetCulture("", CultureInfo.InvariantCulture).PrintToString(d); + actual.Should().Be("123456789,12345"); + } - //1. Исключить из сериализации свойства определенного типа + - //2. Указать альтернативный способ сериализации для определенного типа+ - //3. Для числовых типов указать культуру - //4. Настроить сериализацию конкретного свойства - //5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств) - //6. Исключить из сериализации конкретного свойства - //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию + - //8. ...с конфигурированием + + [Test] + public void ObjectPrinter_Should_Serialize_IEnumerable_Types() + { + var list = new List() { "hello", "world", "!" }; + var printer = ObjectPrinter.For>(); + var actual = printer.PrintToString(list); + actual.Should().Be("\n\thello\n\tworld\n\t!"); + } + + [Test] + public void ObjectPrinter_Should_Serialize_IDictionary_Types() + { + var dict = new Dictionary(); + dict[1] = "hello"; + dict[2] = "world"; + dict[3] = "!"; + + var printer = ObjectPrinter.For>(); + var actual = printer.PrintToString(dict); + actual.Should().Be("\n\t1 = hello\n\t2 = world\n\t3 = !"); + } + + [Test] + public void ObjectPrinter_Should_Ignore_Cycling_Reference() + { + var printer = ObjectPrinter.For(); + var actual = printer.PrintToString(personWithCyclingReference); + actual.Should() + .Be( + "Person\n\tId = b1754c14-d296-4b0f-a09a-030017f4461f\n\tName = Cycle\n\tHeight = 192\n\tAge = 34\n\tParents = \n\t\t\tCycling references"); + } + + + [Test] + public void PrintToString_Extension_Can_Call_From_Object() + { + var actual = simplePerson.PrintToString(); + actual.Should() + .Be( + $"Person\n\tId = 0f8fad5b-d9cb-469f-a165-70867728950e\n\tName = simplePerson\n\tHeight = 180\n\tAge = 45\n\tParents = null"); + } + + [Test] + public void PrintToString_Extension_Can_Call_From_Object_With_Config() + { + var actual = simplePerson.PrintToString(person => person.Exclude(p => p.Name)); + actual.Should() + .Be( + $"Person\n\tId = 0f8fad5b-d9cb-469f-a165-70867728950e\n\tHeight = 180\n\tAge = 45\n\tParents = null"); + } } } \ No newline at end of file From 28e82625c302e1834af559da92e38f5e709aefaa Mon Sep 17 00:00:00 2001 From: RenatZubakin Date: Mon, 25 Dec 2023 23:02:43 +0500 Subject: [PATCH 4/6] refactoring --- ObjectPrinting/PrintingConfig.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index 520d0b9a..68f09010 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -143,7 +143,7 @@ private bool IsExclude(MemberInfo member) return excludedTypes.Contains(member.GetType()) || excludedProperty.Contains(member); } - public PrintingConfig Exclude() + public PrintingConfig Exclude() where TType: Type { excludedTypes.Add(typeof(TType)); return this; From c404994d7b11c4b0194ffb66fba7dd9d62b7d96a Mon Sep 17 00:00:00 2001 From: RenatZubakin Date: Mon, 25 Dec 2023 23:03:52 +0500 Subject: [PATCH 5/6] refactoring 2 --- ObjectPrinting/PrintingConfig.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index 68f09010..520d0b9a 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -143,7 +143,7 @@ private bool IsExclude(MemberInfo member) return excludedTypes.Contains(member.GetType()) || excludedProperty.Contains(member); } - public PrintingConfig Exclude() where TType: Type + public PrintingConfig Exclude() { excludedTypes.Add(typeof(TType)); return this; From 216d295e8da47c225421e2226d5154dc31772d96 Mon Sep 17 00:00:00 2001 From: RenatZubakin Date: Tue, 26 Dec 2023 16:53:06 +0500 Subject: [PATCH 6/6] corrected reviewer's comments --- ObjectPrinting/PrintingConfig.cs | 47 ++++++++++--------- .../Tests/ObjectPrinterAcceptanceTests.cs | 24 +++++++++- 2 files changed, 48 insertions(+), 23 deletions(-) diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index 520d0b9a..dbb737d6 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -25,11 +25,11 @@ public class PrintingConfig private readonly HashSet excludedTypes = new HashSet(); private readonly HashSet excludedProperty = new HashSet(); - private readonly Dictionary customPropertySerialize = - new Dictionary(); + private readonly Dictionary> customPropertySerialize = + new Dictionary>(); - private readonly Dictionary customTypeSerializers = new Dictionary(); - private HashSet visitedObjects = new HashSet(); + private readonly Dictionary> customTypeSerializers = new Dictionary>(); + private readonly HashSet visitedObjects = new HashSet(); public string PrintToString(TOwner obj) @@ -49,19 +49,19 @@ private string PrintToString(object obj, int nestingLevel) return $"Cycling references"; } - visitedObjects.Add(obj); + //visitedObjects.Add(obj); if (FinalTypes.Contains(obj.GetType())) return obj.ToString(); - if (obj is IDictionary) + if (obj is IDictionary dictionary) { - return PrintToStringIDictionary((IDictionary)obj, nestingLevel + 1); + return PrintToStringIDictionary(dictionary, nestingLevel + 1); } - if (obj is IEnumerable) + if (obj is IEnumerable collection) { - return PrintToStringIEnumerable((IEnumerable)obj, nestingLevel + 1); + return PrintToStringIEnumerable(collection, nestingLevel + 1); } return PrintToStringProperties(obj, nestingLevel + 1); @@ -73,6 +73,7 @@ private string PrintToStringProperties(object obj, int nestingLevel) var builder = new StringBuilder(); var objType = obj.GetType(); builder.Append($"{objType.Name}"); + visitedObjects.Add(obj); foreach (var propertyInfo in GetIncludedProperties(objType)) { var value = propertyInfo.GetValue(obj); @@ -81,7 +82,6 @@ private string PrintToStringProperties(object obj, int nestingLevel) : PrintToString(value, nestingLevel + 1); builder.Append('\n' + identation + propertyInfo.Name + " = " + customSerializeStr); } - visitedObjects.Remove(obj); return builder.ToString(); } @@ -90,7 +90,7 @@ private string PrintToStringIEnumerable(IEnumerable collection, int nestingLevel { var identation = new string('\t', nestingLevel); var builder = new StringBuilder(); - + visitedObjects.Add(collection); foreach (var item in collection) { builder.Append('\n' + identation + PrintToString(item, nestingLevel + 1)); @@ -105,6 +105,8 @@ private string PrintToStringIDictionary(IDictionary dictionary, int nestingLevel var identation = new string('\t', nestingLevel); var builder = new StringBuilder(); + visitedObjects.Add(dictionary); + foreach (var key in dictionary.Keys) { builder.Append('\n' + identation + PrintToString(key, nestingLevel + 1) + " = " + @@ -115,18 +117,19 @@ private string PrintToStringIDictionary(IDictionary dictionary, int nestingLevel return builder.ToString(); } - private bool TryUseCustomSerializer(object value, MemberInfo memberInfo, out string str) + private bool TryUseCustomSerializer(object value, PropertyInfo propertyInfo, out string str) { str = null; - if (customPropertySerialize.ContainsKey(memberInfo)) + if (customPropertySerialize.ContainsKey(propertyInfo)) { - str = (string)customPropertySerialize[memberInfo].DynamicInvoke(value); + str = (string)customPropertySerialize[propertyInfo].DynamicInvoke(value); return true; } - if (customTypeSerializers.ContainsKey(memberInfo.GetType())) + var type = propertyInfo.PropertyType; + if (customTypeSerializers.ContainsKey(propertyInfo.PropertyType)) { - str = (string)customTypeSerializers[memberInfo.GetType()].DynamicInvoke(value); + str = (string)customTypeSerializers[propertyInfo.PropertyType].DynamicInvoke(value); return true; } @@ -138,9 +141,9 @@ private IEnumerable GetIncludedProperties(Type type) return type.GetRuntimeProperties().Where(p => !IsExclude(p)); } - private bool IsExclude(MemberInfo member) + private bool IsExclude(PropertyInfo member) { - return excludedTypes.Contains(member.GetType()) || excludedProperty.Contains(member); + return excludedTypes.Contains(member.PropertyType) || excludedProperty.Contains(member); } public PrintingConfig Exclude() @@ -161,7 +164,7 @@ public PrintingConfig Exclude(Expression SetCustomTypeSerializer(Func serializer) { - customTypeSerializers[typeof(TType)] = serializer; + customTypeSerializers[typeof(TType)] = obj =>serializer((TType)obj); return this; } @@ -177,11 +180,13 @@ public PropertyPrintingConfig SerializeByProperty( public PrintingConfig SetCustomPropertySerializer(MemberInfo property, Func serializer) { - customPropertySerialize[property] = serializer; + customPropertySerialize[property] = obj =>serializer((TProperty)obj); return this; } - public PrintingConfig SetCulture(string format, CultureInfo culture) where TType : IFormattable + public PrintingConfig SetCulture(string format, CultureInfo culture) + where TType : IFormattable + { return SetCustomTypeSerializer((TType obj) => obj.ToString(format, culture)); } diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs index 04ae3774..016e1a99 100644 --- a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs +++ b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.Threading.Tasks; using FluentAssertions; using NUnit.Framework; using ObjectPrinting.Extensions; @@ -18,7 +19,7 @@ public void OneTimeSetUp() { simplePerson = new Person { - Id = new Guid("0f8fad5b-d9cb-469f-a165-70867728950e"), Name = "simplePerson", Age = 45, Height = 180 + Id = new Guid("0f8fad5b-d9cb-469f-a165-70867728950e"), Name = "simplePerson", Age = 45, Height = 180, }; personWithCyclingReference = new Person { Id = new Guid("b1754c14-d296-4b0f-a09a-030017f4461f"), Name = "Cycle", Age = 34, Height = 192 }; @@ -60,7 +61,9 @@ public void ObjectPrinter_Should_Set_Custom_Serialize_For_Type() { var printer = ObjectPrinter.For(); var actual = printer.SetCustomTypeSerializer(i => 0.ToString()).PrintToString(simplePerson); - actual.Should().Be(""); + actual.Should() + .Be( + $"Person\n\tId = 0f8fad5b-d9cb-469f-a165-70867728950e\n\tName = simplePerson\n\tHeight = 180\n\tAge = 0\n\tParents = null"); } [Test] @@ -144,5 +147,22 @@ public void PrintToString_Extension_Can_Call_From_Object_With_Config() .Be( $"Person\n\tId = 0f8fad5b-d9cb-469f-a165-70867728950e\n\tHeight = 180\n\tAge = 45\n\tParents = null"); } + + [Test] + public void ObjectPrinter_Parallel_Using_With_Same_Object() + { + var person = new Person() + { Id = new Guid("b1754c14-d296-4b0f-a09a-030027f4461f"), Name = "Parallel", Age = 94, Height = 200 }; + person.Parents = new[] + { personWithCyclingReference, personWithCyclingReference, simplePerson, simplePerson }; + var actual1 = ""; + var actual2 = ""; + Task task1 = Task.Run(() => { actual1 = person.PrintToString(); }); + Task task2 = Task.Run(() => { actual2 = person.PrintToString(); }); + + Task.WaitAll(task1, task2); + + actual1.Should().Be(actual2); + } } } \ No newline at end of file