From e401825ad3ab9b7482eaba68bab0c5c79ee33372 Mon Sep 17 00:00:00 2001 From: Maxim Date: Wed, 11 Dec 2024 19:42:16 +0500 Subject: [PATCH 1/4] Homework solution --- ObjectPrinting/ObjectExtensions.cs | 6 + ObjectPrinting/ObjectPrinter.cs | 12 +- ObjectPrinting/PrintingConfig.cs | 163 +++++++++++++++--- ObjectPrinting/PropertyPrintingConfig.cs | 33 ++++ .../PropertyPrintingConfigExtensions.cs | 29 ++++ .../Tests/ObjectPrinterAcceptanceTests.cs | 49 +++--- ObjectPrinting/Tests/Person.cs | 15 +- fluent-api.sln.DotSettings | 3 + 8 files changed, 250 insertions(+), 60 deletions(-) create mode 100644 ObjectPrinting/ObjectExtensions.cs create mode 100644 ObjectPrinting/PropertyPrintingConfig.cs create mode 100644 ObjectPrinting/PropertyPrintingConfigExtensions.cs diff --git a/ObjectPrinting/ObjectExtensions.cs b/ObjectPrinting/ObjectExtensions.cs new file mode 100644 index 00000000..943adfd8 --- /dev/null +++ b/ObjectPrinting/ObjectExtensions.cs @@ -0,0 +1,6 @@ +namespace ObjectPrinting; + +public static class ObjectExtensions +{ + public static string PrintToString(this T obj) => ObjectPrinter.For().PrintToString(obj); +} \ No newline at end of file diff --git a/ObjectPrinting/ObjectPrinter.cs b/ObjectPrinting/ObjectPrinter.cs index 3c7867c3..513af06c 100644 --- a/ObjectPrinting/ObjectPrinter.cs +++ b/ObjectPrinting/ObjectPrinter.cs @@ -1,10 +1,6 @@ -namespace ObjectPrinting +namespace ObjectPrinting; + +public static class ObjectPrinter { - public class ObjectPrinter - { - public static PrintingConfig For() - { - return new PrintingConfig(); - } - } + public static PrintingConfig For() => new(); } \ No newline at end of file diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index a9e08211..90b7aff4 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -1,41 +1,156 @@ using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; using System.Linq; +using System.Linq.Expressions; using System.Text; -namespace ObjectPrinting + +namespace ObjectPrinting; + +public class PrintingConfig { - public class PrintingConfig + private readonly HashSet excludedTypes = []; + private readonly HashSet excludedProperties = []; + private readonly Dictionary> typeSerializers = new(); + private readonly Dictionary typeCultures = new(); + private readonly Dictionary> propertySerializers = new(); + + public PropertyPrintingConfig Printing() => new(this); + + public PropertyPrintingConfig Printing( + Expression> memberSelector) => + new(this, memberSelector); + + public PrintingConfig Excluding(Expression> memberSelector) + { + if (memberSelector.Body is not MemberExpression memberExpression) + throw new ArgumentException("Expression must be a member expression", nameof(memberSelector)); + excludedProperties.Add(memberExpression.Member.Name); + return this; + } + + internal PrintingConfig Excluding() + { + excludedTypes.Add(typeof(TPropType)); + return this; + } + + public string PrintToString(TOwner obj) => PrintToString(obj, 0); + + public void AddSerializerForType(Func serializer) => + typeSerializers[typeof(TPropType)] = obj => serializer((TPropType)obj); + + public void AddSerializerForProperty(string propertyName, Func serializer) => + propertySerializers[propertyName] = serializer; + + public void AddCultureForType(CultureInfo culture) => typeCultures[typeof(TPropType)] = culture; + + + + private string PrintToString(object? obj, int nestingLevel, HashSet? visitedObjects = null) { - public string PrintToString(TOwner obj) + if (obj == null) + return "null" + Environment.NewLine; + + visitedObjects ??= []; + + if (!visitedObjects.Add(obj)) + return "Cyclic reference!" + Environment.NewLine; + + var finalTypes = new[] { - return PrintToString(obj, 0); + typeof(int), typeof(double), typeof(float), typeof(string), + typeof(DateTime), typeof(TimeSpan) + }; + + var type = obj.GetType(); + + if (typeSerializers.TryGetValue(type, out var serializer)) + return serializer(obj) + Environment.NewLine; + + if (typeCultures.TryGetValue(type, out var culture) && obj is IFormattable formattable) + return formattable.ToString(null, culture) + Environment.NewLine; + + if (finalTypes.Contains(type)) + return obj + Environment.NewLine; + + return obj switch + { + IDictionary dictionary => SerializeDictionary(dictionary, nestingLevel, visitedObjects), + IEnumerable enumerable => SerializeEnumerable(enumerable, nestingLevel, visitedObjects), + _ => SerializeObject(obj, nestingLevel, visitedObjects) + }; + } + + private string SerializeDictionary(IDictionary dictionary, int nestingLevel, HashSet visitedObjects) + { + var dictionaryType = dictionary.GetType().Name; + var indentation = new string('\t', nestingLevel + 1); + var sb = new StringBuilder(); + + sb.AppendLine($"{dictionaryType}:"); + + foreach (DictionaryEntry entry in dictionary) + { + sb.Append(indentation + "Key = " + + PrintToString(entry.Key, nestingLevel + 1, visitedObjects)); + sb.Append(indentation + "Value = " + + PrintToString(entry.Value, nestingLevel + 1, visitedObjects)); } - private string PrintToString(object obj, int nestingLevel) + visitedObjects.Remove(dictionary); + return sb.ToString(); + } + + private string SerializeEnumerable(IEnumerable enumerable, int nestingLevel, HashSet visitedObjects) + { + var collectionType = enumerable.GetType().Name; + var indentation = new string('\t', nestingLevel + 1); + var sb = new StringBuilder(); + + sb.AppendLine($"{collectionType}:"); + + foreach (var element in enumerable) { - //TODO apply configurations - if (obj == null) - return "null" + Environment.NewLine; + sb.Append(indentation + "- " + + PrintToString(element, nestingLevel + 1, visitedObjects)); + } + + visitedObjects.Remove(enumerable); + return sb.ToString(); + } + + private string SerializeObject(object obj, int nestingLevel, HashSet visitedObjects) + { + var type = obj.GetType(); + var indentation = new string('\t', nestingLevel + 1); + var sb = new StringBuilder(); + sb.AppendLine(type.Name); - var finalTypes = new[] + foreach (var propertyInfo in type.GetProperties()) + { + if (excludedTypes.Contains(propertyInfo.PropertyType) || + excludedProperties.Contains(propertyInfo.Name)) + continue; + + var propertyName = propertyInfo.Name; + var propertyValue = propertyInfo.GetValue(obj); + + if (propertySerializers.TryGetValue(propertyName, out var propertySerializer)) { - 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(indentation + propertyName + " = " + + propertySerializer(propertyValue!) + Environment.NewLine); + } + else { - sb.Append(identation + propertyInfo.Name + " = " + - PrintToString(propertyInfo.GetValue(obj), - nestingLevel + 1)); + sb.Append(indentation + propertyName + " = " + + PrintToString(propertyValue, nestingLevel + 1, visitedObjects)); } - return sb.ToString(); } + + visitedObjects.Remove(obj); + return sb.ToString(); } } \ No newline at end of file diff --git a/ObjectPrinting/PropertyPrintingConfig.cs b/ObjectPrinting/PropertyPrintingConfig.cs new file mode 100644 index 00000000..3f04eaff --- /dev/null +++ b/ObjectPrinting/PropertyPrintingConfig.cs @@ -0,0 +1,33 @@ +using System; +using System.Globalization; +using System.Linq.Expressions; + +namespace ObjectPrinting; + +public class PropertyPrintingConfig(PrintingConfig printingConfig, + Expression> memberSelector = null!) : IPropertyPrintingConfig +{ + public Expression> MemberSelector => memberSelector; + + public PrintingConfig Using(Func print) + { + printingConfig.AddSerializerForType(print); + return printingConfig; + } + + public PrintingConfig Using(CultureInfo culture) + { + if (!typeof(TPropType).IsValueType || !typeof(IFormattable).IsAssignableFrom(typeof(TPropType))) + throw new InvalidOperationException($"Can't apply culture for type: {typeof(TPropType)}"); + + printingConfig.AddCultureForType(culture); + return printingConfig; + } + + PrintingConfig IPropertyPrintingConfig.ParentConfig => printingConfig; +} + +public interface IPropertyPrintingConfig +{ + PrintingConfig ParentConfig { get; } +} \ No newline at end of file diff --git a/ObjectPrinting/PropertyPrintingConfigExtensions.cs b/ObjectPrinting/PropertyPrintingConfigExtensions.cs new file mode 100644 index 00000000..a8734091 --- /dev/null +++ b/ObjectPrinting/PropertyPrintingConfigExtensions.cs @@ -0,0 +1,29 @@ +using System; +using System.Linq.Expressions; + +namespace ObjectPrinting; + +public static class PropertyPrintingConfigExtensions +{ + public static string PrintToString(this T obj, Func, PrintingConfig> config) => + config(ObjectPrinter.For()).PrintToString(obj); + + public static PrintingConfig TrimmedToLength( + this PropertyPrintingConfig propConfig, int maxLen) + { + if (maxLen < 0) + throw new ArgumentOutOfRangeException(nameof(maxLen), "Max length must be non-negative!"); + + IPropertyPrintingConfig propertyConfig = propConfig; + var printingConfig = propertyConfig.ParentConfig; + var propertyName = ((MemberExpression)propConfig.MemberSelector.Body).Member.Name; + + printingConfig.AddSerializerForProperty(propertyName, value => + { + var val = value as string ?? string.Empty; + return val.Length > maxLen ? val[..maxLen] : val; + }); + + return printingConfig; + } +} \ No newline at end of file diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs index 4c8b2445..36e9d9bf 100644 --- a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs +++ b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs @@ -1,27 +1,36 @@ -using NUnit.Framework; +using System; +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 { Name = "Alex", Age = 19 }; + var person = new Person { Name = "Alex", Age = 19 }; - var printer = ObjectPrinter.For(); - //1. Исключить из сериализации свойства определенного типа - //2. Указать альтернативный способ сериализации для определенного типа - //3. Для числовых типов указать культуру - //4. Настроить сериализацию конкретного свойства - //5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств) - //6. Исключить из сериализации конкретного свойства - - string s1 = printer.PrintToString(person); + var printer = ObjectPrinter.For() + //1. Исключить из сериализации свойства определенного типа + .Excluding() + //2. Указать альтернативный способ сериализации для определенного типа + .Printing().Using(i => i.ToString("X")) + //3. Для числовых типов указать культуру + .Printing().Using(CultureInfo.InvariantCulture) + //4. Настроить сериализацию конкретного свойства + .Printing(p => p.Name) + //5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств) + .TrimmedToLength(10) + //6. Исключить из сериализации конкретного свойства + .Excluding(p => p.Age); + + var s1 = printer.PrintToString(person); - //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию - //8. ...с конфигурированием - } + //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию + var s2 = person.PrintToString(); + //8. ...с конфигурированием + var s3 = person.PrintToString(s => s.Excluding(p => p.Age)); } } \ No newline at end of file diff --git a/ObjectPrinting/Tests/Person.cs b/ObjectPrinting/Tests/Person.cs index f9555955..9fab80f4 100644 --- a/ObjectPrinting/Tests/Person.cs +++ b/ObjectPrinting/Tests/Person.cs @@ -1,12 +1,11 @@ using System; -namespace ObjectPrinting.Tests +namespace ObjectPrinting.Tests; + +public class Person { - public class Person - { - public Guid Id { get; set; } - public string Name { get; set; } - public double Height { get; set; } - public int Age { get; set; } - } + 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/fluent-api.sln.DotSettings b/fluent-api.sln.DotSettings index 135b83ec..53fe49b2 100644 --- a/fluent-api.sln.DotSettings +++ b/fluent-api.sln.DotSettings @@ -1,6 +1,9 @@  <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb" /> + <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Types and namespaces"><ElementKinds><Kind Name="NAMESPACE" /><Kind Name="CLASS" /><Kind Name="STRUCT" /><Kind Name="ENUM" /><Kind Name="DELEGATE" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb_AaBb" /></Policy> + True True True Imported 10.10.2016 From 1be115bebc5ac1c70da9c2933cf43b7f3866da7f Mon Sep 17 00:00:00 2001 From: Maxim Date: Wed, 11 Dec 2024 21:44:29 +0500 Subject: [PATCH 2/4] Added approval tests --- ObjectPrinting/ObjectPrinting.csproj | 37 ++++ ...ctPrinterAcceptanceTests.Demo.approved.txt | 9 + ..._ShouldDetectCyclicReferences.approved.txt | 17 ++ ...henChainingSerializationRules.approved.txt | 7 + ...WhenExcludingSpecificProperty.approved.txt | 8 + ...xcludingTypeFromSerialization.approved.txt | 8 + ...tly_WhenSerializingDictionary.approved.txt | 15 ++ ...tly_WhenSerializingEnumerable.approved.txt | 27 +++ ...nSerializingWithCustomCulture.approved.txt | 9 + ..._WhenTrimmingStringProperties.approved.txt | 9 + ...ctly_WhenUsingExtensionMethod.approved.txt | 9 + ...rkCorrectly_WithConfiguration.approved.txt | 8 + ...mSerializationForSpecificType.approved.txt | 9 + .../Tests/ObjectPrinterAcceptanceTests.cs | 181 ++++++++++++++++-- ObjectPrinting/Tests/Person.cs | 5 + 15 files changed, 343 insertions(+), 15 deletions(-) create mode 100644 ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.Demo.approved.txt create mode 100644 ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldDetectCyclicReferences.approved.txt create mode 100644 ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WhenChainingSerializationRules.approved.txt create mode 100644 ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WhenExcludingSpecificProperty.approved.txt create mode 100644 ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WhenExcludingTypeFromSerialization.approved.txt create mode 100644 ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WhenSerializingDictionary.approved.txt create mode 100644 ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WhenSerializingEnumerable.approved.txt create mode 100644 ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WhenSerializingWithCustomCulture.approved.txt create mode 100644 ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WhenTrimmingStringProperties.approved.txt create mode 100644 ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WhenUsingExtensionMethod.approved.txt create mode 100644 ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WithConfiguration.approved.txt create mode 100644 ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WithCustomSerializationForSpecificType.approved.txt diff --git a/ObjectPrinting/ObjectPrinting.csproj b/ObjectPrinting/ObjectPrinting.csproj index c5db392f..38f21d13 100644 --- a/ObjectPrinting/ObjectPrinting.csproj +++ b/ObjectPrinting/ObjectPrinting.csproj @@ -5,8 +5,45 @@ + + + + + ObjectPrinterAcceptanceTests.cs + + + ObjectPrinterAcceptanceTests.cs + + + ObjectPrinterAcceptanceTests.cs + + + ObjectPrinterAcceptanceTests.cs + + + ObjectPrinterAcceptanceTests.cs + + + ObjectPrinterAcceptanceTests.cs + + + ObjectPrinterAcceptanceTests.cs + + + ObjectPrinterAcceptanceTests.cs + + + ObjectPrinterAcceptanceTests.cs + + + ObjectPrinterAcceptanceTests.cs + + + ObjectPrinterAcceptanceTests.cs + + diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.Demo.approved.txt b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.Demo.approved.txt new file mode 100644 index 00000000..7720eadd --- /dev/null +++ b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.Demo.approved.txt @@ -0,0 +1,9 @@ +Person + Id = Guid + Name = Alex + Height = 1,81 + Age = 19 + DateOfBirth = 09.09.1985 00:00:00 + Addresses = null + Children = null + Father = null diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldDetectCyclicReferences.approved.txt b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldDetectCyclicReferences.approved.txt new file mode 100644 index 00000000..3d662ca0 --- /dev/null +++ b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldDetectCyclicReferences.approved.txt @@ -0,0 +1,17 @@ +Person + Id = Guid + Name = Alex + Height = 1,81 + Age = 19 + DateOfBirth = 01.01.0001 00:00:00 + Addresses = null + Children = null + Father = Person + Id = Guid + Name = Pavel Doe + Height = 0 + Age = 68 + DateOfBirth = 09.09.1954 00:00:00 + Addresses = null + Children = null + Father = Cyclic reference! diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WhenChainingSerializationRules.approved.txt b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WhenChainingSerializationRules.approved.txt new file mode 100644 index 00000000..2d80b15d --- /dev/null +++ b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WhenChainingSerializationRules.approved.txt @@ -0,0 +1,7 @@ +Person + Name = Maxwe + Height = 1.81 + DateOfBirth = понедельник, 1 января 0001 г. + Addresses = null + Children = null + Father = null diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WhenExcludingSpecificProperty.approved.txt b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WhenExcludingSpecificProperty.approved.txt new file mode 100644 index 00000000..e41aa8de --- /dev/null +++ b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WhenExcludingSpecificProperty.approved.txt @@ -0,0 +1,8 @@ +Person + Id = Guid + Name = Alex + Age = 19 + DateOfBirth = 01.01.0001 00:00:00 + Addresses = null + Children = null + Father = null diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WhenExcludingTypeFromSerialization.approved.txt b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WhenExcludingTypeFromSerialization.approved.txt new file mode 100644 index 00000000..34bd8b56 --- /dev/null +++ b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WhenExcludingTypeFromSerialization.approved.txt @@ -0,0 +1,8 @@ +Person + Id = Guid + Name = Alex + Height = 1,81 + Age = 19 + Addresses = null + Children = null + Father = null diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WhenSerializingDictionary.approved.txt b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WhenSerializingDictionary.approved.txt new file mode 100644 index 00000000..5a3341db --- /dev/null +++ b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WhenSerializingDictionary.approved.txt @@ -0,0 +1,15 @@ +Person + Id = Guid + Name = Alex + Height = 1,81 + Age = 19 + DateOfBirth = 01.01.0001 00:00:00 + Addresses = Dictionary`2: + Key = 1 + Value = London + Key = 2 + Value = New York + Key = 3 + Value = Moscow + Children = null + Father = null diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WhenSerializingEnumerable.approved.txt b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WhenSerializingEnumerable.approved.txt new file mode 100644 index 00000000..1f88bef5 --- /dev/null +++ b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WhenSerializingEnumerable.approved.txt @@ -0,0 +1,27 @@ +Person + Id = Guid + Name = Alex + Height = 1,81 + Age = 19 + DateOfBirth = 01.01.0001 00:00:00 + Addresses = null + Children = List`1: + - Person + Id = Guid + Name = Natasha + Height = 2 + Age = 8 + DateOfBirth = 09.09.2002 00:00:00 + Addresses = null + Children = null + Father = null + - Person + Id = Guid + Name = Pasha + Height = 1 + Age = 9 + DateOfBirth = 09.09.2004 00:00:00 + Addresses = null + Children = null + Father = null + Father = null diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WhenSerializingWithCustomCulture.approved.txt b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WhenSerializingWithCustomCulture.approved.txt new file mode 100644 index 00000000..898e63a0 --- /dev/null +++ b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WhenSerializingWithCustomCulture.approved.txt @@ -0,0 +1,9 @@ +Person + Id = Guid + Name = Alex + Height = 1.81 + Age = 19 + DateOfBirth = 01.01.0001 00:00:00 + Addresses = null + Children = null + Father = null diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WhenTrimmingStringProperties.approved.txt b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WhenTrimmingStringProperties.approved.txt new file mode 100644 index 00000000..43777d58 --- /dev/null +++ b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WhenTrimmingStringProperties.approved.txt @@ -0,0 +1,9 @@ +Person + Id = Guid + Name = Al + Height = 1,81 + Age = 19 + DateOfBirth = 01.01.0001 00:00:00 + Addresses = null + Children = null + Father = null diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WhenUsingExtensionMethod.approved.txt b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WhenUsingExtensionMethod.approved.txt new file mode 100644 index 00000000..ed3539cb --- /dev/null +++ b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WhenUsingExtensionMethod.approved.txt @@ -0,0 +1,9 @@ +Person + Id = Guid + Name = Alex + Height = 1,81 + Age = 19 + DateOfBirth = 01.01.0001 00:00:00 + Addresses = null + Children = null + Father = null diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WithConfiguration.approved.txt b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WithConfiguration.approved.txt new file mode 100644 index 00000000..9599a2a8 --- /dev/null +++ b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WithConfiguration.approved.txt @@ -0,0 +1,8 @@ +Person + Id = Guid + Name = Alex + Height = 1,81 + DateOfBirth = 01.01.0001 00:00:00 + Addresses = null + Children = null + Father = null diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WithCustomSerializationForSpecificType.approved.txt b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WithCustomSerializationForSpecificType.approved.txt new file mode 100644 index 00000000..40937f92 --- /dev/null +++ b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WithCustomSerializationForSpecificType.approved.txt @@ -0,0 +1,9 @@ +Person + Id = Guid + Name = Alex + Height = 1,81 + Age = 19 + DateOfBirth = четверг, 9 сентября 2004 г. + Addresses = null + Children = null + Father = null diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs index 36e9d9bf..afc55614 100644 --- a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs +++ b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs @@ -1,5 +1,8 @@ using System; +using System.Collections.Generic; using System.Globalization; +using ApprovalTests; +using ApprovalTests.Reporters; using NUnit.Framework; namespace ObjectPrinting.Tests; @@ -7,30 +10,178 @@ namespace ObjectPrinting.Tests; [TestFixture] public class ObjectPrinterAcceptanceTests { + private Person person; + + [SetUp] + public void Setup() => person = new Person { Name = "Alex", Age = 19, Height = 1.81 }; + [Test] + [UseReporter(typeof(DiffReporter))] public void Demo() { - var person = new Person { Name = "Alex", Age = 19 }; + person.DateOfBirth = new DateTime(1985, 9, 9); + var printer = ObjectPrinter.For(); + var s = printer.PrintToString(person); - var printer = ObjectPrinter.For() - //1. Исключить из сериализации свойства определенного типа + Approvals.Verify(s); + } + + [Test] + [UseReporter(typeof(DiffReporter))] + public void PrintToString_ShouldWorkCorrectly_WhenExcludingTypeFromSerialization() + { + person.DateOfBirth = new DateTime(2004, 9, 9); + var printer = ObjectPrinter + .For() + .Excluding(); //1. Исключить из сериализации свойства определенного типа + + var s = printer.PrintToString(person); + + Approvals.Verify(s); + } + + [Test] + [UseReporter(typeof(DiffReporter))] + public void PrintToString_ShouldWorkCorrectly_WithCustomSerializationForSpecificType() + { + person.DateOfBirth = new DateTime(2004, 9, 9); + var printer = ObjectPrinter + .For() + .Printing().Using(i => i.ToLongDateString()); + //2. Указать альтернативный способ сериализации для определенного типа + + var s = printer.PrintToString(person); + + Approvals.Verify(s); + Console.WriteLine(s); + } + + [Test] + [UseReporter(typeof(DiffReporter))] + public void PrintToString_ShouldWorkCorrectly_WhenSerializingWithCustomCulture() + { + var printer = ObjectPrinter + .For() + .Printing().Using(CultureInfo.InvariantCulture); + //3. Для числовых типов указать культуру + + var s = printer.PrintToString(person); + + Approvals.Verify(s); + } + + [Test] + [UseReporter(typeof(DiffReporter))] + public void PrintToString_ShouldWorkCorrectly_WhenTrimmingStringProperties() + { + var printer = ObjectPrinter + .For() + .Printing(p => p.Name) //4. Настроить сериализацию конкретного свойства + .TrimmedToLength(2); + //5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств) + + var s = printer.PrintToString(person); + + Approvals.Verify(s); + } + + [Test] + [UseReporter(typeof(DiffReporter))] + public void PrintToString_ShouldWorkCorrectly_WhenExcludingSpecificProperty() + { + var printer = ObjectPrinter + .For() + .Excluding(p => p.Height); + //6. Исключение из сериализации конкретного свойства/поля + + var s = printer.PrintToString(person); + + Approvals.Verify(s); + } + [Test] + [UseReporter(typeof(DiffReporter))] + public void PrintToString_ShouldWorkCorrectly_WhenChainingSerializationRules() + { + person.Name = "Maxwell"; + var printer = ObjectPrinter + .For() .Excluding() - //2. Указать альтернативный способ сериализации для определенного типа - .Printing().Using(i => i.ToString("X")) - //3. Для числовых типов указать культуру + .Printing().Using(i => i.ToLongDateString()) .Printing().Using(CultureInfo.InvariantCulture) - //4. Настроить сериализацию конкретного свойства - .Printing(p => p.Name) - //5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств) - .TrimmedToLength(10) - //6. Исключить из сериализации конкретного свойства + .Printing(p => p.Name).TrimmedToLength(5) .Excluding(p => p.Age); - var s1 = printer.PrintToString(person); + var s = printer.PrintToString(person); + + Approvals.Verify(s); + } + + [Test] + [UseReporter(typeof(DiffReporter))] + public void PrintToString_ShouldWorkCorrectly_WhenUsingExtensionMethod() + { + var s = person.PrintToString(); + //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию - //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию - var s2 = person.PrintToString(); + Approvals.Verify(s); + } + + [Test] + [UseReporter(typeof(DiffReporter))] + public void PrintToString_ShouldWorkCorrectly_WithConfiguration() + { + var s = person.PrintToString(s => s.Excluding(p => p.Age)); //8. ...с конфигурированием - var s3 = person.PrintToString(s => s.Excluding(p => p.Age)); + + Approvals.Verify(s); + } + + [Test] + [UseReporter(typeof(DiffReporter))] + public void PrintToString_ShouldDetectCyclicReferences() + { + var father = new Person + { + Name = "Pavel Doe", + Age = 68, + DateOfBirth = new DateTime(1954, 9, 9), + Father = person + }; + person.Father = father; + + var s = person.PrintToString(); + + Approvals.Verify(s); + } + + [Test] + [UseReporter(typeof(DiffReporter))] + public void PrintToString_ShouldWorkCorrectly_WhenSerializingDictionary() + { + person.Addresses = new Dictionary + { + { 1, "London" }, + { 2, "New York" }, + { 3, "Moscow" } + }; + + var s = person.PrintToString(); + + Approvals.Verify(s); + } + + [Test] + [UseReporter(typeof(DiffReporter))] + public void PrintToString_ShouldWorkCorrectly_WhenSerializingEnumerable() + { + person.Children = + [ + new Person { Name = "Natasha", Age = 8, Height = 2, DateOfBirth = new DateTime(2002, 9 , 9) }, + new Person { Name = "Pasha", Age = 9, Height = 1, DateOfBirth = new DateTime(2004, 9 , 9) }, + ]; + + var s = person.PrintToString(); + + Approvals.Verify(s); } } \ No newline at end of file diff --git a/ObjectPrinting/Tests/Person.cs b/ObjectPrinting/Tests/Person.cs index 9fab80f4..7d2b19f2 100644 --- a/ObjectPrinting/Tests/Person.cs +++ b/ObjectPrinting/Tests/Person.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; namespace ObjectPrinting.Tests; @@ -8,4 +9,8 @@ public class Person public string Name { get; set; } public double Height { get; set; } public int Age { get; set; } + public DateTime DateOfBirth { get; set; } + public Dictionary Addresses { get; set; } + public List Children { get; set; } + public Person Father { get; set; } } \ No newline at end of file From a14bd7a507467c07b271176bf252645ba998fa72 Mon Sep 17 00:00:00 2001 From: Maxim Date: Wed, 11 Dec 2024 23:57:47 +0500 Subject: [PATCH 3/4] small fix --- ObjectPrinting/PrintingConfig.cs | 52 +++++++++---------- ...tly_WhenSerializingEnumerable.approved.txt | 8 +-- .../Tests/ObjectPrinterAcceptanceTests.cs | 4 +- 3 files changed, 30 insertions(+), 34 deletions(-) diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index 90b7aff4..80ba3cbc 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -2,7 +2,6 @@ using System.Collections; using System.Collections.Generic; using System.Globalization; -using System.Linq; using System.Linq.Expressions; using System.Text; @@ -16,6 +15,13 @@ public class PrintingConfig private readonly Dictionary> typeSerializers = new(); private readonly Dictionary typeCultures = new(); private readonly Dictionary> propertySerializers = new(); + private readonly HashSet visitedObjects = []; + + private static readonly HashSet FinalTypes = + [ + typeof(int), typeof(double), typeof(float), typeof(string), + typeof(DateTime), typeof(TimeSpan) + ]; public PropertyPrintingConfig Printing() => new(this); @@ -46,26 +52,16 @@ public void AddSerializerForProperty(string propertyName, Func s propertySerializers[propertyName] = serializer; public void AddCultureForType(CultureInfo culture) => typeCultures[typeof(TPropType)] = culture; - - - - private string PrintToString(object? obj, int nestingLevel, HashSet? visitedObjects = null) + + private string PrintToString(object? obj, int nestingLevel) { if (obj == null) return "null" + Environment.NewLine; - - visitedObjects ??= []; - - if (!visitedObjects.Add(obj)) - return "Cyclic reference!" + Environment.NewLine; - - var finalTypes = new[] - { - typeof(int), typeof(double), typeof(float), typeof(string), - typeof(DateTime), typeof(TimeSpan) - }; - + var type = obj.GetType(); + + if (!visitedObjects.Add(obj) && !FinalTypes.Contains(type)) + return "Cyclic reference!" + Environment.NewLine; if (typeSerializers.TryGetValue(type, out var serializer)) return serializer(obj) + Environment.NewLine; @@ -73,18 +69,18 @@ private string PrintToString(object? obj, int nestingLevel, HashSet? vis if (typeCultures.TryGetValue(type, out var culture) && obj is IFormattable formattable) return formattable.ToString(null, culture) + Environment.NewLine; - if (finalTypes.Contains(type)) + if (FinalTypes.Contains(type)) return obj + Environment.NewLine; return obj switch { - IDictionary dictionary => SerializeDictionary(dictionary, nestingLevel, visitedObjects), - IEnumerable enumerable => SerializeEnumerable(enumerable, nestingLevel, visitedObjects), - _ => SerializeObject(obj, nestingLevel, visitedObjects) + IDictionary dictionary => SerializeDictionary(dictionary, nestingLevel), + IEnumerable enumerable => SerializeEnumerable(enumerable, nestingLevel), + _ => SerializeObject(obj, nestingLevel) }; } - private string SerializeDictionary(IDictionary dictionary, int nestingLevel, HashSet visitedObjects) + private string SerializeDictionary(IDictionary dictionary, int nestingLevel) { var dictionaryType = dictionary.GetType().Name; var indentation = new string('\t', nestingLevel + 1); @@ -95,16 +91,16 @@ private string SerializeDictionary(IDictionary dictionary, int nestingLevel, Has foreach (DictionaryEntry entry in dictionary) { sb.Append(indentation + "Key = " + - PrintToString(entry.Key, nestingLevel + 1, visitedObjects)); + PrintToString(entry.Key, nestingLevel + 1)); sb.Append(indentation + "Value = " + - PrintToString(entry.Value, nestingLevel + 1, visitedObjects)); + PrintToString(entry.Value, nestingLevel + 1)); } visitedObjects.Remove(dictionary); return sb.ToString(); } - private string SerializeEnumerable(IEnumerable enumerable, int nestingLevel, HashSet visitedObjects) + private string SerializeEnumerable(IEnumerable enumerable, int nestingLevel) { var collectionType = enumerable.GetType().Name; var indentation = new string('\t', nestingLevel + 1); @@ -115,14 +111,14 @@ private string SerializeEnumerable(IEnumerable enumerable, int nestingLevel, Has foreach (var element in enumerable) { sb.Append(indentation + "- " + - PrintToString(element, nestingLevel + 1, visitedObjects)); + PrintToString(element, nestingLevel + 1)); } visitedObjects.Remove(enumerable); return sb.ToString(); } - private string SerializeObject(object obj, int nestingLevel, HashSet visitedObjects) + private string SerializeObject(object obj, int nestingLevel) { var type = obj.GetType(); var indentation = new string('\t', nestingLevel + 1); @@ -146,7 +142,7 @@ private string SerializeObject(object obj, int nestingLevel, HashSet vis else { sb.Append(indentation + propertyName + " = " + - PrintToString(propertyValue, nestingLevel + 1, visitedObjects)); + PrintToString(propertyValue, nestingLevel + 1)); } } diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WhenSerializingEnumerable.approved.txt b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WhenSerializingEnumerable.approved.txt index 1f88bef5..5fed252b 100644 --- a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WhenSerializingEnumerable.approved.txt +++ b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.PrintToString_ShouldWorkCorrectly_WhenSerializingEnumerable.approved.txt @@ -9,7 +9,7 @@ - Person Id = Guid Name = Natasha - Height = 2 + Height = 0 Age = 8 DateOfBirth = 09.09.2002 00:00:00 Addresses = null @@ -18,9 +18,9 @@ - Person Id = Guid Name = Pasha - Height = 1 - Age = 9 - DateOfBirth = 09.09.2004 00:00:00 + Height = 0 + Age = 8 + DateOfBirth = 09.09.2002 00:00:00 Addresses = null Children = null Father = null diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs index afc55614..f3ea68fb 100644 --- a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs +++ b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs @@ -176,8 +176,8 @@ public void PrintToString_ShouldWorkCorrectly_WhenSerializingEnumerable() { person.Children = [ - new Person { Name = "Natasha", Age = 8, Height = 2, DateOfBirth = new DateTime(2002, 9 , 9) }, - new Person { Name = "Pasha", Age = 9, Height = 1, DateOfBirth = new DateTime(2004, 9 , 9) }, + new Person { Name = "Natasha", Age = 8, DateOfBirth = new DateTime(2002, 9 , 9) }, + new Person { Name = "Pasha", Age = 8, DateOfBirth = new DateTime(2002, 9 , 9) }, ]; var s = person.PrintToString(); From f2a5208785a09e02e5892edcc582a64b69d76d09 Mon Sep 17 00:00:00 2001 From: Maxim Date: Thu, 12 Dec 2024 00:10:10 +0500 Subject: [PATCH 4/4] another small fix --- ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs index f3ea68fb..ac1c644a 100644 --- a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs +++ b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs @@ -53,7 +53,6 @@ public void PrintToString_ShouldWorkCorrectly_WithCustomSerializationForSpecific var s = printer.PrintToString(person); Approvals.Verify(s); - Console.WriteLine(s); } [Test]