From cc5e1aeeb3085503ff8f467618f4d29aad8610db Mon Sep 17 00:00:00 2001 From: crycrash Date: Tue, 10 Dec 2024 19:11:34 +0500 Subject: [PATCH 1/4] Added realization and tests --- ObjectPrintingHomework/ObjectPrinter.cs | 9 ++ .../ObjectPrintingExtension.cs | 14 ++ .../ObjectPrintingHomework.csproj | 9 ++ ObjectPrintingHomework/Person.cs | 14 ++ ObjectPrintingHomework/PrintingConfig.cs | 109 +++++++++++++ ObjectPrintingHomework/PropertyConfig.cs | 43 +++++ .../ObjectPrinterAcceptanceTestsHomework.cs | 31 ++++ .../TestsObjectPrinting.csproj | 29 ++++ .../TestsObjectPrintingHomework.cs | 150 ++++++++++++++++++ fluent-api.sln | 12 ++ 10 files changed, 420 insertions(+) create mode 100644 ObjectPrintingHomework/ObjectPrinter.cs create mode 100644 ObjectPrintingHomework/ObjectPrintingExtension.cs create mode 100644 ObjectPrintingHomework/ObjectPrintingHomework.csproj create mode 100644 ObjectPrintingHomework/Person.cs create mode 100644 ObjectPrintingHomework/PrintingConfig.cs create mode 100644 ObjectPrintingHomework/PropertyConfig.cs create mode 100644 TestsObjectPrinting/ObjectPrinterAcceptanceTestsHomework.cs create mode 100644 TestsObjectPrinting/TestsObjectPrinting.csproj create mode 100644 TestsObjectPrinting/TestsObjectPrintingHomework.cs diff --git a/ObjectPrintingHomework/ObjectPrinter.cs b/ObjectPrintingHomework/ObjectPrinter.cs new file mode 100644 index 00000000..a2a8fffe --- /dev/null +++ b/ObjectPrintingHomework/ObjectPrinter.cs @@ -0,0 +1,9 @@ +namespace ObjectPrintingHomework; + +public class ObjectPrinter +{ + public static PrintingConfig For() + { + return new PrintingConfig(); + } +} \ No newline at end of file diff --git a/ObjectPrintingHomework/ObjectPrintingExtension.cs b/ObjectPrintingHomework/ObjectPrintingExtension.cs new file mode 100644 index 00000000..8946e88f --- /dev/null +++ b/ObjectPrintingHomework/ObjectPrintingExtension.cs @@ -0,0 +1,14 @@ +namespace ObjectPrintingHomework; +public static class ObjectPrinterExtensions +{ + public static string PrintToString(this TOwner obj) + { + return ObjectPrinter.For().PrintToString(obj); + } + + public static string PrintToString(this TOwner obj, Func, PrintingConfig> config) + { + var printer = config(ObjectPrinter.For()); + return printer.PrintToString(obj); + } +} \ No newline at end of file diff --git a/ObjectPrintingHomework/ObjectPrintingHomework.csproj b/ObjectPrintingHomework/ObjectPrintingHomework.csproj new file mode 100644 index 00000000..fa71b7ae --- /dev/null +++ b/ObjectPrintingHomework/ObjectPrintingHomework.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + + diff --git a/ObjectPrintingHomework/Person.cs b/ObjectPrintingHomework/Person.cs new file mode 100644 index 00000000..4d7c3ff7 --- /dev/null +++ b/ObjectPrintingHomework/Person.cs @@ -0,0 +1,14 @@ +using System.Diagnostics.Contracts; + +namespace ObjectPrintingHomework; + +public class Person +{ + public Guid Id { get; set; } + public string Name { get; set; } + public double Height { get; set; } + public int Age { get; set; } + public int CountEyes { get; set; } + public string Surname { get; set; } + public DateTime DateBirth {get; set; } +} \ No newline at end of file diff --git a/ObjectPrintingHomework/PrintingConfig.cs b/ObjectPrintingHomework/PrintingConfig.cs new file mode 100644 index 00000000..8149137d --- /dev/null +++ b/ObjectPrintingHomework/PrintingConfig.cs @@ -0,0 +1,109 @@ +using System.Globalization; +using System.Linq.Expressions; +using System.Text; + +namespace ObjectPrintingHomework; +public class PrintingConfig +{ + private readonly List excludedTypes = []; + private readonly List excludedProperties = []; + private readonly Dictionary> typeSerializers = []; + private readonly Dictionary> propertySerializers = []; + private readonly Dictionary typeCultures = []; + private readonly Dictionary stringPropertyLengths = []; + + public string PrintToString(TOwner obj) + { + return PrintToString(obj, 0); + } + + public PrintingConfig Excluding() + { + excludedTypes.Add(typeof(TPropType)); + return this; + } + public PrintingConfig Excluding(Expression> memberSelector) + { + if (memberSelector.Body is MemberExpression memberExpression) + excludedProperties.Add(memberExpression.Member.Name); + return this; + } + + public PropertyPrintingConfig Printing() + { + return new PropertyPrintingConfig(this); + } + + public PropertyPrintingConfig Printing(Expression> memberSelector) + { + var propertyName = ((MemberExpression)memberSelector.Body).Member.Name; + return new PropertyPrintingConfig(this, propertyName); + } + + private string PrintToString(object obj, int nestingLevel) + { + if (obj == null) + return "null" + Environment.NewLine; + var type = obj.GetType(); + + if (excludedTypes.Contains(type)) + return string.Empty; + + if (typeSerializers.ContainsKey(type)) + return typeSerializers[type](obj) + Environment.NewLine; + + if (typeCultures.TryGetValue(type, out var culture) && obj is IFormattable formattable) + return formattable.ToString(null, culture) + Environment.NewLine; + + var finalTypes = new[] + { + typeof(int), typeof(double), typeof(float), typeof(string), + typeof(DateTime), typeof(TimeSpan) + }; + if (finalTypes.Contains(obj.GetType())) + return obj + Environment.NewLine; + + var identation = new string('\t', nestingLevel + 1); + var sb = new StringBuilder(); + sb.AppendLine(type.Name); + foreach (var propertyInfo in type.GetProperties()) + { + var propertyType = propertyInfo.PropertyType; + var propertyName = propertyInfo.Name; + var propertyValue = propertyInfo.GetValue(obj); + + if (propertySerializers.ContainsKey(propertyName)) + return propertySerializers[propertyName](propertyValue) + Environment.NewLine; + + if (propertyValue is string stringValue && stringPropertyLengths.TryGetValue(propertyInfo.Name, out var maxLength)) + propertyValue = stringValue[..Math.Min(maxLength, stringValue.Length)]; + + if (excludedTypes.Contains(propertyType) || excludedProperties.Contains(propertyName)) + continue; + sb.Append(identation + propertyName + " = " + + PrintToString(propertyValue, + nestingLevel + 1)); + } + return sb.ToString().Trim(); + } + + public void AddTypeSerializer(Func serializer) + { + typeSerializers[typeof(TPropType)] = obj => serializer((TPropType)obj); + } + + public void AddPropertySerializer(string propertyName, Func serializer) + { + propertySerializers[propertyName] = serializer; + } + + public void SetCulture(CultureInfo culture) + { + typeCultures[typeof(TPropType)] = culture; + } + + public void SetStringPropertyLength(string propertyName, int maxLength) + { + stringPropertyLengths[propertyName] = maxLength; + } +} \ No newline at end of file diff --git a/ObjectPrintingHomework/PropertyConfig.cs b/ObjectPrintingHomework/PropertyConfig.cs new file mode 100644 index 00000000..2120cd93 --- /dev/null +++ b/ObjectPrintingHomework/PropertyConfig.cs @@ -0,0 +1,43 @@ +using System.Globalization; + +namespace ObjectPrintingHomework; + +public class PropertyPrintingConfig +{ + private readonly PrintingConfig parentConfig; + private readonly string propertyName; + + public PropertyPrintingConfig(PrintingConfig parentConfig, string propertyName = null) + { + this.parentConfig = parentConfig; + this.propertyName = propertyName; + } + + public PrintingConfig Using(Func serializer) + { + if (propertyName == null) + parentConfig.AddTypeSerializer(serializer); + else + parentConfig.AddPropertySerializer(propertyName, obj => serializer((TPropType)obj)); + + return parentConfig; + } + + public PrintingConfig Using(Func culture) + { + parentConfig.SetCulture(culture(default!)); + return parentConfig; + } + + public PrintingConfig TrimmedToLength(int maxLength) + { + if (typeof(TPropType) != typeof(string)) + throw new InvalidOperationException("Trimming is only supported for string properties"); + + if (propertyName == null) + throw new InvalidOperationException("Property name must be specified for trimming"); + + parentConfig.SetStringPropertyLength(propertyName, maxLength); + return parentConfig; + } +} \ No newline at end of file diff --git a/TestsObjectPrinting/ObjectPrinterAcceptanceTestsHomework.cs b/TestsObjectPrinting/ObjectPrinterAcceptanceTestsHomework.cs new file mode 100644 index 00000000..312d19e0 --- /dev/null +++ b/TestsObjectPrinting/ObjectPrinterAcceptanceTestsHomework.cs @@ -0,0 +1,31 @@ +using System.Globalization; +using ObjectPrintingHomework; + +namespace TestsObjectPrinting; + +[TestFixture] +public class ObjectPrinterAcceptanceTests +{ + [Test] + public void Demo() + { + var person = new Person { Name = "Alex", Age = 19 }; + + var printer = ObjectPrinter.For() + //1. Исключить из сериализации свойства определенного типа + .Excluding() + //2. Указать альтернативный способ сериализации для определенного типа + .Printing().Using(i => i.ToString("X")) + //3. Для числовых типов указать культуру + .Printing().Using(i => new CultureInfo("ar-EG")) + //4. Настроить сериализацию конкретного свойства + .Printing(p => p.Height).Using(i => new CultureInfo("ar-EG")) + //5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств) + .Printing(p => p.Name).TrimmedToLength(10) + //6. Исключить из сериализации конкретного свойства + .Excluding(p => p.Age); + + string s1 = printer.PrintToString(person); + Console.WriteLine(s1); + } +} \ No newline at end of file diff --git a/TestsObjectPrinting/TestsObjectPrinting.csproj b/TestsObjectPrinting/TestsObjectPrinting.csproj new file mode 100644 index 00000000..bb06b73a --- /dev/null +++ b/TestsObjectPrinting/TestsObjectPrinting.csproj @@ -0,0 +1,29 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + + + diff --git a/TestsObjectPrinting/TestsObjectPrintingHomework.cs b/TestsObjectPrinting/TestsObjectPrintingHomework.cs new file mode 100644 index 00000000..224f425a --- /dev/null +++ b/TestsObjectPrinting/TestsObjectPrintingHomework.cs @@ -0,0 +1,150 @@ +using ObjectPrintingHomework; +using FluentAssertions; +using System.Globalization; + +namespace TestsObjectPrinting; +public class TestsObjectPrinting +{ + private Person person; + [SetUp] + public void Setup() + { + person = new Person { Name = "Milana", Age = 20, Height = 164.4, Surname = "Zinovieva", CountEyes = 2, DateBirth = new DateTime(2004, 09, 19, 12, 30, 45) }; + } + + [Test] + public void TestOneExcludingType() + { + const string notExcepted = nameof(Person.Name) + " = "; + var result = ObjectPrinter.For() + .Excluding() + .PrintToString(person); + + result.Should().NotContain(notExcepted); + } + + [Test] + public void TestAllExcludingTypes() + { + const string excepted = "Person"; + var result = ObjectPrinter.For() + .Excluding().Excluding().Excluding().Excluding().Excluding() + .PrintToString(person); + + result.Should().Be(excepted); + } + + [Test] + public void TestExcludingGeneralType() + { + var result = ObjectPrinter.For() + .Excluding() + .PrintToString(person); + + result.Should().BeEmpty(); + } + + [Test] + public void TestExcludingProperty() + { + const string notExcepted = nameof(Person.Age) + " = "; + var result = ObjectPrinter.For() + .Excluding(p => p.Age) + .PrintToString(person); + + result.Should().NotContain(notExcepted); + } + + [Test] + public void TestExcludingAllProperties() + { + const string excepted = "Person"; + var result = ObjectPrinter.For() + .Excluding(p => p.Age).Excluding(p => p.CountEyes).Excluding(p => p.Height) + .Excluding(p => p.Id).Excluding(p => p.Name).Excluding(p => p.Surname).Excluding(p => p.DateBirth) + .PrintToString(person); + + result.Should().Be(excepted); + } + + [Test] + public void TestAnotherSerializationForString() + { + const string excepted = " serialization"; + var result = ObjectPrinter.For().Printing().Using(p => p + " serialization") + .PrintToString(person); + + result.Should().Contain(excepted); + } + + [Test] + public void TestAnotherSerializationForInt() + { + const string exceptedForAge = "Age = 0"; + const string exceptedForEyes = "CountEyes = 0"; + var result = ObjectPrinter.For().Printing().Using(p => "0") + .PrintToString(person); + + result.Should().Contain(exceptedForAge).And.Contain(exceptedForEyes); + } + + [Test] + public void TestSetCultureForDouble() + { + const string excepted = "164.4"; + const string unexcepted = "164,4"; + var result = ObjectPrinter.For().Printing().Using(i => new CultureInfo("en-GB")) + .PrintToString(person); + + result.Should().NotContain(unexcepted).And.Contain(excepted); + } + + [Test] + public void TestSetCultureForDateTime() + { + const string unexcepted = "19.09.2004 12:30:45"; + const string excepted = "19/09/2004 12:30:45"; + var result = ObjectPrinter.For().Printing().Using(i => new CultureInfo("fr")) + .PrintToString(person); + + result.Should().NotContain(unexcepted).And.Contain(excepted); + } + + [Test] + public void TestAnotherSerializationForName() + { + const string unexcepted = "Milana"; + const string excepted = "MILANA"; + var result = ObjectPrinter.For() + .Printing(p => p.Name) + .Using(p => p.ToUpper()) + .PrintToString(person); + + Console.WriteLine(result); + result.Should().NotContain(unexcepted).And.Contain(excepted); + } + + [Test] + public void TestTrimmingSurname() + { + const string unexcepted = "Zinovieva"; + const string excepted = "Zin"; + var result = ObjectPrinter.For() + .Printing(p => p.Surname).TrimmedToLength(3) + .PrintToString(person); + + Console.WriteLine(result); + result.Should().NotContain(unexcepted).And.Contain(excepted); + } + + [Test] + public void TestExceptionForTrimming() + { + Action action = () => ObjectPrinter.For() + .Printing(p => p.Age) + .TrimmedToLength(3); + + action.Should().Throw() + .WithMessage("Trimming is only supported for string properties"); + } +} \ No newline at end of file diff --git a/fluent-api.sln b/fluent-api.sln index 69c8db9e..af0c513b 100644 --- a/fluent-api.sln +++ b/fluent-api.sln @@ -13,6 +13,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FluentMapping.Tests", "Samp EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Spectacle", "Samples\Spectacle\Spectacle.csproj", "{EFA9335C-411B-4597-B0B6-5438D1AE04C3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestsObjectPrinting", "TestsObjectPrinting\TestsObjectPrinting.csproj", "{1FA74E0A-A32D-4014-9EB1-8C748CB11E96}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ObjectPrintingHomework", "ObjectPrintingHomework\ObjectPrintingHomework.csproj", "{0215A567-D0CC-43B3-916A-68A8DBF6B529}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -35,6 +39,14 @@ Global {EFA9335C-411B-4597-B0B6-5438D1AE04C3}.Debug|Any CPU.Build.0 = Debug|Any CPU {EFA9335C-411B-4597-B0B6-5438D1AE04C3}.Release|Any CPU.ActiveCfg = Release|Any CPU {EFA9335C-411B-4597-B0B6-5438D1AE04C3}.Release|Any CPU.Build.0 = Release|Any CPU + {1FA74E0A-A32D-4014-9EB1-8C748CB11E96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1FA74E0A-A32D-4014-9EB1-8C748CB11E96}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1FA74E0A-A32D-4014-9EB1-8C748CB11E96}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1FA74E0A-A32D-4014-9EB1-8C748CB11E96}.Release|Any CPU.Build.0 = Release|Any CPU + {0215A567-D0CC-43B3-916A-68A8DBF6B529}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0215A567-D0CC-43B3-916A-68A8DBF6B529}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0215A567-D0CC-43B3-916A-68A8DBF6B529}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0215A567-D0CC-43B3-916A-68A8DBF6B529}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 2039d70197e2fe901d972cdf68423252d188b152 Mon Sep 17 00:00:00 2001 From: crycrash Date: Tue, 10 Dec 2024 19:27:21 +0500 Subject: [PATCH 2/4] Refactoring and cycle reference --- ObjectPrintingHomework/Person.cs | 2 ++ ObjectPrintingHomework/PrintingConfig.cs | 11 ++++++++--- .../TestsObjectPrintingHomework.cs | 18 ++++++++++++++---- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/ObjectPrintingHomework/Person.cs b/ObjectPrintingHomework/Person.cs index 4d7c3ff7..91c03566 100644 --- a/ObjectPrintingHomework/Person.cs +++ b/ObjectPrintingHomework/Person.cs @@ -11,4 +11,6 @@ public class Person public int CountEyes { get; set; } public string Surname { get; set; } public DateTime DateBirth {get; set; } + public Person[] Parents { get; set; } + public Person[] Friends { get; set; } } \ No newline at end of file diff --git a/ObjectPrintingHomework/PrintingConfig.cs b/ObjectPrintingHomework/PrintingConfig.cs index 8149137d..39f6a058 100644 --- a/ObjectPrintingHomework/PrintingConfig.cs +++ b/ObjectPrintingHomework/PrintingConfig.cs @@ -11,6 +11,7 @@ public class PrintingConfig private readonly Dictionary> propertySerializers = []; private readonly Dictionary typeCultures = []; private readonly Dictionary stringPropertyLengths = []; + private readonly HashSet processedObjects = new HashSet(); public string PrintToString(TOwner obj) { @@ -46,6 +47,10 @@ private string PrintToString(object obj, int nestingLevel) return "null" + Environment.NewLine; var type = obj.GetType(); + if (processedObjects.Contains(obj)) + return "Circular Reference" + Environment.NewLine; + processedObjects.Add(obj); + if (excludedTypes.Contains(type)) return string.Empty; @@ -57,9 +62,9 @@ private string PrintToString(object obj, int nestingLevel) var finalTypes = new[] { - typeof(int), typeof(double), typeof(float), typeof(string), - typeof(DateTime), typeof(TimeSpan) - }; + typeof(int), typeof(double), typeof(float), typeof(string), + typeof(DateTime), typeof(TimeSpan) + }; if (finalTypes.Contains(obj.GetType())) return obj + Environment.NewLine; diff --git a/TestsObjectPrinting/TestsObjectPrintingHomework.cs b/TestsObjectPrinting/TestsObjectPrintingHomework.cs index 224f425a..8720ef29 100644 --- a/TestsObjectPrinting/TestsObjectPrintingHomework.cs +++ b/TestsObjectPrinting/TestsObjectPrintingHomework.cs @@ -28,7 +28,7 @@ public void TestAllExcludingTypes() { const string excepted = "Person"; var result = ObjectPrinter.For() - .Excluding().Excluding().Excluding().Excluding().Excluding() + .Excluding().Excluding().Excluding().Excluding().Excluding().Excluding() .PrintToString(person); result.Should().Be(excepted); @@ -61,7 +61,7 @@ public void TestExcludingAllProperties() const string excepted = "Person"; var result = ObjectPrinter.For() .Excluding(p => p.Age).Excluding(p => p.CountEyes).Excluding(p => p.Height) - .Excluding(p => p.Id).Excluding(p => p.Name).Excluding(p => p.Surname).Excluding(p => p.DateBirth) + .Excluding(p => p.Id).Excluding(p => p.Name).Excluding(p => p.Surname).Excluding(p => p.DateBirth).Excluding() .PrintToString(person); result.Should().Be(excepted); @@ -120,7 +120,6 @@ public void TestAnotherSerializationForName() .Using(p => p.ToUpper()) .PrintToString(person); - Console.WriteLine(result); result.Should().NotContain(unexcepted).And.Contain(excepted); } @@ -133,7 +132,6 @@ public void TestTrimmingSurname() .Printing(p => p.Surname).TrimmedToLength(3) .PrintToString(person); - Console.WriteLine(result); result.Should().NotContain(unexcepted).And.Contain(excepted); } @@ -147,4 +145,16 @@ public void TestExceptionForTrimming() action.Should().Throw() .WithMessage("Trimming is only supported for string properties"); } + + [Test] + public void Work_WhenReferenceCycles() + { + const string excepted = "Circular Reference"; + person.Parents = [person]; + person.Friends = [person]; + var result = ObjectPrinter.For().PrintToString(person); + + Console.WriteLine(result); + result.Should().Contain(excepted); + } } \ No newline at end of file From e05b307468f703278088c5aec2b8ca4d711cad1d Mon Sep 17 00:00:00 2001 From: crycrash Date: Wed, 11 Dec 2024 18:01:17 +0500 Subject: [PATCH 3/4] Fixed --- ObjectPrintingHomework/PrintingConfig.cs | 58 ++++++++++--------- ObjectPrintingHomework/PropertyConfig.cs | 4 +- .../ObjectPrinterAcceptanceTestsHomework.cs | 2 +- .../TestsObjectPrintingHomework.cs | 21 +++++-- 4 files changed, 50 insertions(+), 35 deletions(-) diff --git a/ObjectPrintingHomework/PrintingConfig.cs b/ObjectPrintingHomework/PrintingConfig.cs index 39f6a058..59863a74 100644 --- a/ObjectPrintingHomework/PrintingConfig.cs +++ b/ObjectPrintingHomework/PrintingConfig.cs @@ -10,8 +10,8 @@ public class PrintingConfig private readonly Dictionary> typeSerializers = []; private readonly Dictionary> propertySerializers = []; private readonly Dictionary typeCultures = []; - private readonly Dictionary stringPropertyLengths = []; - private readonly HashSet processedObjects = new HashSet(); + private readonly Dictionary> stringPropertyLengths = []; + private readonly HashSet processedObjects = []; public string PrintToString(TOwner obj) { @@ -25,8 +25,9 @@ public PrintingConfig Excluding() } public PrintingConfig Excluding(Expression> memberSelector) { - if (memberSelector.Body is MemberExpression memberExpression) - excludedProperties.Add(memberExpression.Member.Name); + if (memberSelector.Body is not MemberExpression memberExpression) + throw new ArgumentException("Needed MemberExpression"); + excludedProperties.Add(memberExpression.Member.Name); return this; } @@ -44,52 +45,53 @@ public PropertyPrintingConfig Printing(Expression< private string PrintToString(object obj, int nestingLevel) { if (obj == null) - return "null" + Environment.NewLine; + return string.Empty; + var type = obj.GetType(); if (processedObjects.Contains(obj)) - return "Circular Reference" + Environment.NewLine; - processedObjects.Add(obj); + return "Circular Reference"; if (excludedTypes.Contains(type)) return string.Empty; + processedObjects.Add(obj); - if (typeSerializers.ContainsKey(type)) - return typeSerializers[type](obj) + Environment.NewLine; + if (typeSerializers.TryGetValue(type, out var serializer)) + return serializer(obj); if (typeCultures.TryGetValue(type, out var culture) && obj is IFormattable formattable) - return formattable.ToString(null, culture) + Environment.NewLine; + return formattable.ToString(null, culture); - var finalTypes = new[] - { - typeof(int), typeof(double), typeof(float), typeof(string), - typeof(DateTime), typeof(TimeSpan) - }; - if (finalTypes.Contains(obj.GetType())) - return obj + Environment.NewLine; + if (type.IsSerializable && type.Namespace.StartsWith("System")) + return obj.ToString(); - var identation = new string('\t', nestingLevel + 1); + var indentation = new string('\t', nestingLevel + 1); var sb = new StringBuilder(); sb.AppendLine(type.Name); + foreach (var propertyInfo in type.GetProperties()) { var propertyType = propertyInfo.PropertyType; var propertyName = propertyInfo.Name; var propertyValue = propertyInfo.GetValue(obj); - if (propertySerializers.ContainsKey(propertyName)) - return propertySerializers[propertyName](propertyValue) + Environment.NewLine; + if (propertySerializers.TryGetValue(propertyName, out var propertySerializer)) + { + sb.AppendLine(propertySerializer(propertyValue)); + continue; + } - if (propertyValue is string stringValue && stringPropertyLengths.TryGetValue(propertyInfo.Name, out var maxLength)) - propertyValue = stringValue[..Math.Min(maxLength, stringValue.Length)]; + if (propertyValue is string stringValue && stringPropertyLengths.TryGetValue(propertyName, out var length)) + propertyValue = stringValue.Substring(Math.Min(length.Item1, stringValue.Length))[..Math.Min(length.Item2, stringValue.Length)]; if (excludedTypes.Contains(propertyType) || excludedProperties.Contains(propertyName)) continue; - sb.Append(identation + propertyName + " = " + - PrintToString(propertyValue, - nestingLevel + 1)); + + sb.Append(indentation + propertyName + " = "); + sb.AppendLine(PrintToString(propertyValue, nestingLevel + 1)); } - return sb.ToString().Trim(); + + return sb.ToString(); } public void AddTypeSerializer(Func serializer) @@ -107,8 +109,8 @@ public void SetCulture(CultureInfo culture) typeCultures[typeof(TPropType)] = culture; } - public void SetStringPropertyLength(string propertyName, int maxLength) + public void SetStringPropertyLength(string propertyName, int startIndex, int maxLength) { - stringPropertyLengths[propertyName] = maxLength; + stringPropertyLengths[propertyName] = new Tuple(startIndex, maxLength); } } \ No newline at end of file diff --git a/ObjectPrintingHomework/PropertyConfig.cs b/ObjectPrintingHomework/PropertyConfig.cs index 2120cd93..4113a827 100644 --- a/ObjectPrintingHomework/PropertyConfig.cs +++ b/ObjectPrintingHomework/PropertyConfig.cs @@ -29,7 +29,7 @@ public PrintingConfig Using(Func culture) return parentConfig; } - public PrintingConfig TrimmedToLength(int maxLength) + public PrintingConfig TrimmedToLength(int startIndex, int maxLength) { if (typeof(TPropType) != typeof(string)) throw new InvalidOperationException("Trimming is only supported for string properties"); @@ -37,7 +37,7 @@ public PrintingConfig TrimmedToLength(int maxLength) if (propertyName == null) throw new InvalidOperationException("Property name must be specified for trimming"); - parentConfig.SetStringPropertyLength(propertyName, maxLength); + parentConfig.SetStringPropertyLength(propertyName, startIndex, maxLength); return parentConfig; } } \ No newline at end of file diff --git a/TestsObjectPrinting/ObjectPrinterAcceptanceTestsHomework.cs b/TestsObjectPrinting/ObjectPrinterAcceptanceTestsHomework.cs index 312d19e0..26ca0c1f 100644 --- a/TestsObjectPrinting/ObjectPrinterAcceptanceTestsHomework.cs +++ b/TestsObjectPrinting/ObjectPrinterAcceptanceTestsHomework.cs @@ -21,7 +21,7 @@ public void Demo() //4. Настроить сериализацию конкретного свойства .Printing(p => p.Height).Using(i => new CultureInfo("ar-EG")) //5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств) - .Printing(p => p.Name).TrimmedToLength(10) + .Printing(p => p.Name).TrimmedToLength(0, 10) //6. Исключить из сериализации конкретного свойства .Excluding(p => p.Age); diff --git a/TestsObjectPrinting/TestsObjectPrintingHomework.cs b/TestsObjectPrinting/TestsObjectPrintingHomework.cs index 8720ef29..a34a24eb 100644 --- a/TestsObjectPrinting/TestsObjectPrintingHomework.cs +++ b/TestsObjectPrinting/TestsObjectPrintingHomework.cs @@ -31,7 +31,7 @@ public void TestAllExcludingTypes() .Excluding().Excluding().Excluding().Excluding().Excluding().Excluding() .PrintToString(person); - result.Should().Be(excepted); + result.Trim().Should().Be(excepted); } [Test] @@ -64,7 +64,7 @@ public void TestExcludingAllProperties() .Excluding(p => p.Id).Excluding(p => p.Name).Excluding(p => p.Surname).Excluding(p => p.DateBirth).Excluding() .PrintToString(person); - result.Should().Be(excepted); + result.Trim().Should().Be(excepted); } [Test] @@ -129,7 +129,19 @@ public void TestTrimmingSurname() const string unexcepted = "Zinovieva"; const string excepted = "Zin"; var result = ObjectPrinter.For() - .Printing(p => p.Surname).TrimmedToLength(3) + .Printing(p => p.Surname).TrimmedToLength(0, 3) + .PrintToString(person); + + result.Should().NotContain(unexcepted).And.Contain(excepted); + } + + [Test] + public void TestTrimmingSurnameInMiddle() + { + const string unexcepted = "Zinovieva"; + const string excepted = "inov"; + var result = ObjectPrinter.For() + .Printing(p => p.Surname).TrimmedToLength(1, 4) .PrintToString(person); result.Should().NotContain(unexcepted).And.Contain(excepted); @@ -140,12 +152,13 @@ public void TestExceptionForTrimming() { Action action = () => ObjectPrinter.For() .Printing(p => p.Age) - .TrimmedToLength(3); + .TrimmedToLength(0, 3); action.Should().Throw() .WithMessage("Trimming is only supported for string properties"); } + [Test] public void Work_WhenReferenceCycles() { From 563080f4ab298d090a2ef5f142f6e3b54ab9edd7 Mon Sep 17 00:00:00 2001 From: crycrash Date: Wed, 11 Dec 2024 20:19:21 +0500 Subject: [PATCH 4/4] Processing arrays, dictionaries and lists --- ObjectPrintingHomework/Person.cs | 2 + ObjectPrintingHomework/PrintingConfig.cs | 35 ++++++++++++ .../TestsObjectPrintingHomework.cs | 53 +++++++++++++++++-- 3 files changed, 87 insertions(+), 3 deletions(-) diff --git a/ObjectPrintingHomework/Person.cs b/ObjectPrintingHomework/Person.cs index 91c03566..73acb340 100644 --- a/ObjectPrintingHomework/Person.cs +++ b/ObjectPrintingHomework/Person.cs @@ -13,4 +13,6 @@ public class Person public DateTime DateBirth {get; set; } public Person[] Parents { get; set; } public Person[] Friends { get; set; } + public Dictionary LimbToNumbersFingers {get; set;} + public List Childs {get; set;} } \ No newline at end of file diff --git a/ObjectPrintingHomework/PrintingConfig.cs b/ObjectPrintingHomework/PrintingConfig.cs index 59863a74..4a805c21 100644 --- a/ObjectPrintingHomework/PrintingConfig.cs +++ b/ObjectPrintingHomework/PrintingConfig.cs @@ -1,3 +1,5 @@ +using System.Collections; +using System.Diagnostics.Metrics; using System.Globalization; using System.Linq.Expressions; using System.Text; @@ -56,6 +58,9 @@ private string PrintToString(object obj, int nestingLevel) return string.Empty; processedObjects.Add(obj); + if (obj is ICollection) + return ProcessCollections(obj, nestingLevel); + if (typeSerializers.TryGetValue(type, out var serializer)) return serializer(obj); @@ -113,4 +118,34 @@ public void SetStringPropertyLength(string propertyName, int startIndex, int max { stringPropertyLengths[propertyName] = new Tuple(startIndex, maxLength); } + + private string ProcessCollections(object obj, int nestingLevel){ + if (obj is IDictionary dictionary) + return ExecuteDictionary(dictionary, nestingLevel); + else{ + var collection = (IEnumerable)obj; + return ExecuteIEnumerable(collection, nestingLevel); + } + } + + private string ExecuteDictionary(IDictionary dictionary, int nestingLevel) + { + var sb = new StringBuilder(); + foreach(var key in dictionary.Keys){ + sb.Append("{" + PrintToString(key, nestingLevel + 1) + " = "); + sb.Append(PrintToString(dictionary[key], nestingLevel + 1) + "}; "); + } + return sb.ToString(); + } + + private string ExecuteIEnumerable(IEnumerable collection, int nestingLevel) + { + var sb = new StringBuilder(); + sb.Append('\n'); + foreach(var value in collection){ + sb.Append('\t', nestingLevel + 1); + sb.Append(PrintToString(value, nestingLevel + 1)); + } + return sb.ToString(); + } } \ No newline at end of file diff --git a/TestsObjectPrinting/TestsObjectPrintingHomework.cs b/TestsObjectPrinting/TestsObjectPrintingHomework.cs index a34a24eb..b917a9d7 100644 --- a/TestsObjectPrinting/TestsObjectPrintingHomework.cs +++ b/TestsObjectPrinting/TestsObjectPrintingHomework.cs @@ -28,7 +28,9 @@ public void TestAllExcludingTypes() { const string excepted = "Person"; var result = ObjectPrinter.For() - .Excluding().Excluding().Excluding().Excluding().Excluding().Excluding() + .Excluding().Excluding().Excluding() + .Excluding().Excluding().Excluding() + .Excluding>().Excluding>() .PrintToString(person); result.Trim().Should().Be(excepted); @@ -61,7 +63,9 @@ public void TestExcludingAllProperties() const string excepted = "Person"; var result = ObjectPrinter.For() .Excluding(p => p.Age).Excluding(p => p.CountEyes).Excluding(p => p.Height) - .Excluding(p => p.Id).Excluding(p => p.Name).Excluding(p => p.Surname).Excluding(p => p.DateBirth).Excluding() + .Excluding(p => p.Id).Excluding(p => p.Name).Excluding(p => p.Surname) + .Excluding(p => p.DateBirth).Excluding(p => p.Friends).Excluding(p => p.LimbToNumbersFingers) + .Excluding(p => p.Parents).Excluding(p => p.Childs) .PrintToString(person); result.Trim().Should().Be(excepted); @@ -160,7 +164,7 @@ public void TestExceptionForTrimming() [Test] - public void Work_WhenReferenceCycles() + public void TestWhenReferenceCycles() { const string excepted = "Circular Reference"; person.Parents = [person]; @@ -170,4 +174,47 @@ public void Work_WhenReferenceCycles() Console.WriteLine(result); result.Should().Contain(excepted); } + + [Test] + public void TestSerializingDictionary() + { + const string excepted = "{Left_arm = 5}; {Left_leg = 6}; {Right_leg = 10};"; + Dictionary dict = new Dictionary{ + {"Left_arm", 5}, {"Left_leg", 6}, {"Right_leg", 10} + }; + person.LimbToNumbersFingers = dict; + var result = ObjectPrinter.For().PrintToString(person); + + result.Should().Contain(excepted); + } + + [Test] + public void TestSerializingList() + { + const string exceptedOleg = "Name = Oleg\n\t\t\tAge = 1"; + const string exceptedMaria = "Name = Maria\n\t\t\tAge = 2"; + List list = new List{new Person { Name = "Oleg", Age = 1} , new Person { Name = "Maria", Age = 2}}; + person.Childs = list; + var result = ObjectPrinter.For().Excluding(p => p.CountEyes).Excluding(p => p.Height) + .Excluding(p => p.Id).Excluding(p => p.Surname) + .Excluding(p => p.DateBirth).Excluding(p => p.Friends).Excluding(p => p.LimbToNumbersFingers) + .Excluding(p => p.Parents).PrintToString(person); + + result.Should().Contain(exceptedOleg).And.Contain(exceptedMaria); + } + + [Test] + public void TestSerializingArray() + { + const string exceptedAlbert = "Name = Albert\n\t\t\tAge = 54"; + const string exceptedLiana = "Name = Liana\n\t\t\tAge = 55"; + Person[] list = [new Person { Name = "Albert", Age = 54} , new Person { Name = "Liana", Age = 55}]; + person.Parents = list; + var result = ObjectPrinter.For().Excluding(p => p.CountEyes).Excluding(p => p.Height) + .Excluding(p => p.Id).Excluding(p => p.Surname) + .Excluding(p => p.DateBirth).Excluding(p => p.Friends).Excluding(p => p.LimbToNumbersFingers) + .Excluding(p => p.Childs).PrintToString(person); + + result.Should().Contain(exceptedAlbert).And.Contain(exceptedLiana); + } } \ No newline at end of file