From a6cdbdf3bf2cb8e66e9a776761f8da8e847bf731 Mon Sep 17 00:00:00 2001 From: Foma Medvedev Date: Thu, 6 Nov 2025 20:14:10 +0500 Subject: [PATCH 1/7] =?UTF-8?q?=D0=98=D1=81=D0=BA=D0=BB=D1=8E=D1=87=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D1=81=D0=B2=D0=BE=D0=B9=D1=81=D1=82=D0=B2?= =?UTF-8?q?=20=D1=82=D0=B8=D0=BF=D0=B0=20=D0=B8=20=D0=BA=D0=B0=D1=81=D1=82?= =?UTF-8?q?=D0=BE=D0=BC=D0=BD=D0=B0=D1=8F=20=D1=81=D0=B5=D1=80=D0=B8=D0=B0?= =?UTF-8?q?=D0=BB=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ObjectPrinting/PrintingConfig.cs | 53 ++++++++++------- ObjectPrinting/Serializers.cs | 57 +++++++++++++++++++ .../Tests/ObjectPrinterAcceptanceTests.cs | 29 +++++++++- ObjectPrinting/Tests/Person.cs | 8 +++ fluent-api.sln.DotSettings | 3 + 5 files changed, 128 insertions(+), 22 deletions(-) create mode 100644 ObjectPrinting/Serializers.cs diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index a9e082117..5db56afc1 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -1,11 +1,36 @@ using System; +using System.Collections.Generic; using System.Linq; -using System.Text; namespace ObjectPrinting { public class PrintingConfig { + public readonly IEnumerable ExcludedTypes; + public readonly Dictionary CustomSerializers; + public PrintingConfig() : this( + new List(), + new Dictionary()) + {} + + private PrintingConfig(IEnumerable excludedTypes, Dictionary customSerializers) + { + this.ExcludedTypes = excludedTypes; + this.CustomSerializers = customSerializers; + } + + public PrintingConfig ExcludePropertyOfType() + { + return new PrintingConfig(ExcludedTypes.Append(typeof(T)), CustomSerializers); + } + + public PrintingConfig WithTypeSerializtionType(ISerializer serializer) + { + var tempDict = new Dictionary(CustomSerializers); + tempDict.Add(typeof(T), serializer); + return new PrintingConfig(ExcludedTypes, tempDict); + } + public string PrintToString(TOwner obj) { return PrintToString(obj, 0); @@ -17,25 +42,15 @@ private string PrintToString(object obj, int nestingLevel) if (obj == null) return "null" + Environment.NewLine; - var finalTypes = new[] - { - typeof(int), typeof(double), typeof(float), typeof(string), - typeof(DateTime), typeof(TimeSpan) - }; - if (finalTypes.Contains(obj.GetType())) - return obj + Environment.NewLine; - - var identation = new string('\t', nestingLevel + 1); - var sb = new StringBuilder(); var type = obj.GetType(); - sb.AppendLine(type.Name); - foreach (var propertyInfo in type.GetProperties()) - { - sb.Append(identation + propertyInfo.Name + " = " + - PrintToString(propertyInfo.GetValue(obj), - nestingLevel + 1)); - } - return sb.ToString(); + return GetSerializerForType(type).Serialize(obj, nestingLevel, this); + } + + public ISerializer GetSerializerForType(Type type) + { + if (CustomSerializers.ContainsKey(type)) + return CustomSerializers[type]; + return new Serializer(); } } } \ No newline at end of file diff --git a/ObjectPrinting/Serializers.cs b/ObjectPrinting/Serializers.cs new file mode 100644 index 000000000..c644fcd22 --- /dev/null +++ b/ObjectPrinting/Serializers.cs @@ -0,0 +1,57 @@ +using System; +using System.Linq; +using System.Text; + +namespace ObjectPrinting; + +public interface ISerializer +{ + public string Serialize(object obj, int nestingLevel, PrintingConfig config); +} + +public class Serializer : ISerializer +{ + public string Serialize(object obj, int nestingLevel, PrintingConfig config) + { + if (obj == null) + return "null" + Environment.NewLine; + var type = obj.GetType(); + var finalTypes = new[] + { + typeof(int), typeof(double), typeof(float), typeof(string), + typeof(DateTime), typeof(TimeSpan) + }; + if (finalTypes.Contains(type)) + { + return obj + Environment.NewLine; + } + var identation = new string('\t', nestingLevel + 1); + var sb = new StringBuilder(); + sb.AppendLine(type.Name); + var properties = type.GetProperties() + .Where(x => !config.ExcludedTypes.Contains(x.PropertyType)); + foreach (var propertyInfo in properties) + { + sb.Append(identation + propertyInfo.Name + " = " + + config.GetSerializerForType(propertyInfo.PropertyType).Serialize(propertyInfo.GetValue(obj), + nestingLevel + 1, config)); + } + return sb.ToString(); + } +} + +public class GuidSerializer : ISerializer +{ + public string Serialize(object obj, int nestingLevel, PrintingConfig config) + { + return "999999999" + Environment.NewLine; + } +} + +public class MyItem : ISerializer +{ + public string Serialize(object obj, int nestingLevel, PrintingConfig config) + { + return "inner serializer" + Environment.NewLine; + } +} \ No newline at end of file diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs index 4c8b2445c..6dbad956f 100644 --- a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs +++ b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs @@ -1,4 +1,5 @@ -using NUnit.Framework; +using System; +using NUnit.Framework; namespace ObjectPrinting.Tests { @@ -10,7 +11,13 @@ public void Demo() { var person = new Person { Name = "Alex", Age = 19 }; - var printer = ObjectPrinter.For(); + var printer = ObjectPrinter.For() + .ExcludePropertyOfType(); + //.WithTypeSerializtionType(new PersonSerializer()) + // .SetDigitsCulture() + // .WtihPropertySerializationType(property_name) + // .TrimString() + // .ExcludeProperty(property_name); //1. Исключить из сериализации свойства определенного типа //2. Указать альтернативный способ сериализации для определенного типа //3. Для числовых типов указать культуру @@ -19,9 +26,25 @@ public void Demo() //6. Исключить из сериализации конкретного свойства string s1 = printer.PrintToString(person); + Console.WriteLine(s1); - //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию + //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию //8. ...с конфигурированием } + + [Test] + public void ClassWithNestedClassMember() + { + var person = new Person { Name = "Alex", Age = 19 }; + var test = new TestClass { Id = Guid.NewGuid(), Name = "AlexTest" }; + person.TestClass = test; + var person2 = new Person { Name = "Alexxxxxxxxxxxx", Age = 21 }; + test.Person = person2; + var printer = ObjectPrinter.For() + .ExcludePropertyOfType(); + //.WithTypeSerializtionType(new MyItem()); + string s1 = printer.PrintToString(person); + Console.WriteLine(s1); + } } } \ No newline at end of file diff --git a/ObjectPrinting/Tests/Person.cs b/ObjectPrinting/Tests/Person.cs index f95559554..7a1c6f1dc 100644 --- a/ObjectPrinting/Tests/Person.cs +++ b/ObjectPrinting/Tests/Person.cs @@ -8,5 +8,13 @@ public class Person public string Name { get; set; } public double Height { get; set; } public int Age { get; set; } + public TestClass TestClass { get; set; } + } + + public class TestClass + { + public Guid Id { get; set; } + public string Name { get; set; } + public Person Person { get; set; } } } \ No newline at end of file diff --git a/fluent-api.sln.DotSettings b/fluent-api.sln.DotSettings index 135b83ecb..53fe49b2f 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 85c2ea97d9ce001cbbabd3ced91d9c2da8dca990 Mon Sep 17 00:00:00 2001 From: Foma Medvedev Date: Sun, 16 Nov 2025 20:31:01 +0500 Subject: [PATCH 2/7] =?UTF-8?q?=D0=97=D0=B0=D0=B2=D0=B5=D1=80=D1=88=D0=B8?= =?UTF-8?q?=D0=BB=20=D1=80=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D1=8E=20=D0=B1=D0=B0=D0=B7=D0=BE=D0=B2=D1=8B=D1=85=20=D0=BC?= =?UTF-8?q?=D0=B5=D1=82=D0=BE=D0=B4=D0=BE=D0=B2=20=D0=B8=20=D0=B4=D0=BE?= =?UTF-8?q?=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20=D1=82=D0=B5=D1=81=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ObjectPrinting/ObjectPrinterExtensions.cs | 17 ++ ObjectPrinting/ObjectPrinting.csproj | 3 +- ObjectPrinting/PrintingConfig.cs | 150 ++++++++++++++++-- ObjectPrinting/PropertyPrintingConfig.cs | 20 +++ .../PropertyPrintingConfigExtensions.cs | 25 +++ ObjectPrinting/Serializers.cs | 46 +++++- .../Tests/ObjectPrinterAcceptanceTests.cs | 147 ++++++++++++++--- ObjectPrinting/Tests/Person.cs | 7 +- 8 files changed, 373 insertions(+), 42 deletions(-) create mode 100644 ObjectPrinting/ObjectPrinterExtensions.cs create mode 100644 ObjectPrinting/PropertyPrintingConfig.cs create mode 100644 ObjectPrinting/PropertyPrintingConfigExtensions.cs diff --git a/ObjectPrinting/ObjectPrinterExtensions.cs b/ObjectPrinting/ObjectPrinterExtensions.cs new file mode 100644 index 000000000..6461e7eae --- /dev/null +++ b/ObjectPrinting/ObjectPrinterExtensions.cs @@ -0,0 +1,17 @@ +using System; + +namespace ObjectPrinting; + +public static class ObjectPrinterExtensions +{ + public static string PrintToString(this T obj) + { + return ObjectPrinter.For().PrintToString(obj); + } + + public static string PrintToString(this T obj, Func, PrintingConfig> configBuilder) + { + var config = configBuilder(ObjectPrinter.For()); + return config.PrintToString(obj); + } +} \ No newline at end of file diff --git a/ObjectPrinting/ObjectPrinting.csproj b/ObjectPrinting/ObjectPrinting.csproj index c5db392ff..8cf1eff64 100644 --- a/ObjectPrinting/ObjectPrinting.csproj +++ b/ObjectPrinting/ObjectPrinting.csproj @@ -5,8 +5,9 @@ + - + diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index 5db56afc1..2077f9eb1 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -1,34 +1,136 @@ using System; using System.Collections.Generic; -using System.Linq; +using System.Globalization; +using System.Linq.Expressions; namespace ObjectPrinting { public class PrintingConfig { - public readonly IEnumerable ExcludedTypes; - public readonly Dictionary CustomSerializers; + public readonly HashSet ExcludedTypes; + public readonly Dictionary TypeSerializers; + public readonly HashSet SerializedObjects; + public readonly Dictionary CustomCultureInfos; + public readonly Dictionary PropertySerializers; + public readonly Dictionary MaxStringLength; + public readonly HashSet ExcludedProperties; public PrintingConfig() : this( - new List(), - new Dictionary()) + new HashSet(), + new Dictionary(), + new (ReferenceEqualityComparer.Instance), + new Dictionary(), + new Dictionary(), + new Dictionary(), + new HashSet()) {} - private PrintingConfig(IEnumerable excludedTypes, Dictionary customSerializers) + private PrintingConfig( + HashSet excludedTypes, + Dictionary typeSerializers, + HashSet serializedObjects, + Dictionary customCultureInfos, + Dictionary propertySerializers, + Dictionary maxStringLength, + HashSet excludedProperties) { this.ExcludedTypes = excludedTypes; - this.CustomSerializers = customSerializers; + this.TypeSerializers = typeSerializers; + this.SerializedObjects = serializedObjects; + this.CustomCultureInfos = customCultureInfos; + this.PropertySerializers = propertySerializers; + this.MaxStringLength = maxStringLength; + this.ExcludedProperties = excludedProperties; } public PrintingConfig ExcludePropertyOfType() { - return new PrintingConfig(ExcludedTypes.Append(typeof(T)), CustomSerializers); + var tempSet = new HashSet(ExcludedTypes); + tempSet.Add(typeof(T)); + return new PrintingConfig(tempSet, + TypeSerializers, + SerializedObjects, + CustomCultureInfos, + PropertySerializers, + MaxStringLength, + ExcludedProperties); } public PrintingConfig WithTypeSerializtionType(ISerializer serializer) { - var tempDict = new Dictionary(CustomSerializers); + var tempDict = new Dictionary(TypeSerializers); tempDict.Add(typeof(T), serializer); - return new PrintingConfig(ExcludedTypes, tempDict); + return new PrintingConfig(ExcludedTypes, + tempDict, + SerializedObjects, + CustomCultureInfos, + PropertySerializers, + MaxStringLength, + ExcludedProperties); + } + + public PrintingConfig SetTypeCulture(CultureInfo cultureInfo) + where T : IFormattable + { + var tempDict = new Dictionary(CustomCultureInfos); + tempDict.Add(typeof(T), cultureInfo); + return new PrintingConfig(ExcludedTypes, + TypeSerializers, + SerializedObjects, + tempDict, + PropertySerializers, + MaxStringLength, + ExcludedProperties); + } + + public PrintingConfig WithPropertySerialization(string propertyName, ISerializer serializer) + { + var tempDict = new Dictionary(PropertySerializers); + tempDict.Add(propertyName, serializer); + return new PrintingConfig(ExcludedTypes, + TypeSerializers, + SerializedObjects, + CustomCultureInfos, + tempDict, + MaxStringLength, + ExcludedProperties); + } + + public PrintingConfig TrimStringToLength(string propertyName, int length) + { + var tempDict = new Dictionary(MaxStringLength); + tempDict.Add(propertyName, length); + return new PrintingConfig(ExcludedTypes, + TypeSerializers, + SerializedObjects, + CustomCultureInfos, + PropertySerializers, + tempDict, + ExcludedProperties); + } + + public PrintingConfig Exclude(string propertyName) + { + var tempSet = new HashSet(ExcludedProperties); + tempSet.Add(propertyName); + return new PrintingConfig(ExcludedTypes, + TypeSerializers, + SerializedObjects, + CustomCultureInfos, + PropertySerializers, + MaxStringLength, + tempSet); + } + + public PropertyPrintingConfig GetProperty( + Expression> targetProperty) + { + return new PropertyPrintingConfig(this, targetProperty); + } + + public PropertyPrintingConfig Exclude( + Expression> targetProperty) + { + return new PropertyPrintingConfig(this, targetProperty); } public string PrintToString(TOwner obj) @@ -39,7 +141,7 @@ public string PrintToString(TOwner obj) private string PrintToString(object obj, int nestingLevel) { //TODO apply configurations - if (obj == null) + if (obj is null) return "null" + Environment.NewLine; var type = obj.GetType(); @@ -48,9 +150,31 @@ private string PrintToString(object obj, int nestingLevel) public ISerializer GetSerializerForType(Type type) { - if (CustomSerializers.ContainsKey(type)) - return CustomSerializers[type]; + if (TypeSerializers.ContainsKey(type)) + return TypeSerializers[type]; return new Serializer(); } + + public bool IsPropertyNeedsUniqueSerialization(string propertyName, out ISerializer serializer) + { + serializer = null; + if (PropertySerializers.ContainsKey(propertyName)) + { + serializer = PropertySerializers[propertyName]; + return true; + } + return false; + } + + public bool IsPropertyNeedsTrim(string propertyName, out int trimLength) + { + trimLength = 0; + if (MaxStringLength.ContainsKey(propertyName)) + { + trimLength = MaxStringLength[propertyName]; + return true; + } + return false; + } } } \ No newline at end of file diff --git a/ObjectPrinting/PropertyPrintingConfig.cs b/ObjectPrinting/PropertyPrintingConfig.cs new file mode 100644 index 000000000..b20e3d5ad --- /dev/null +++ b/ObjectPrinting/PropertyPrintingConfig.cs @@ -0,0 +1,20 @@ +using System; +using System.Linq.Expressions; + +namespace ObjectPrinting; + +public class PropertyPrintingConfig +{ + private readonly PrintingConfig _config; + private Expression>? _propertySelector; + + public PropertyPrintingConfig(PrintingConfig config, Expression>? propertySelector) + { + this._config = config; + this._propertySelector = propertySelector; + } + + public PrintingConfig ParentConfig => _config; + + public string PropertyName => ((MemberExpression)_propertySelector.Body).Member.Name; +} \ No newline at end of file diff --git a/ObjectPrinting/PropertyPrintingConfigExtensions.cs b/ObjectPrinting/PropertyPrintingConfigExtensions.cs new file mode 100644 index 000000000..5d3a8af87 --- /dev/null +++ b/ObjectPrinting/PropertyPrintingConfigExtensions.cs @@ -0,0 +1,25 @@ +namespace ObjectPrinting; + +public static class PropertyPrintingConfigExtensions +{ + public static PrintingConfig SetSerializationType + (this PropertyPrintingConfig propertyConfig, ISerializer serializer) + { + var propertyName = propertyConfig.PropertyName; + return propertyConfig.ParentConfig.WithPropertySerialization(propertyName, serializer); + } + + public static PrintingConfig TrimStringToLength + (this PropertyPrintingConfig propertyConfig, int length) + { + var propertyName = propertyConfig.PropertyName; + return propertyConfig.ParentConfig.TrimStringToLength(propertyName, length); + } + + public static PrintingConfig Exclude + (this PropertyPrintingConfig propertyConfig) + { + var propertyName = propertyConfig.PropertyName; + return propertyConfig.ParentConfig.Exclude(propertyName); + } +} \ No newline at end of file diff --git a/ObjectPrinting/Serializers.cs b/ObjectPrinting/Serializers.cs index c644fcd22..0c5a0187a 100644 --- a/ObjectPrinting/Serializers.cs +++ b/ObjectPrinting/Serializers.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Linq; using System.Text; @@ -13,8 +14,10 @@ public class Serializer : ISerializer { public string Serialize(object obj, int nestingLevel, PrintingConfig config) { - if (obj == null) + if (obj is null) return "null" + Environment.NewLine; + if (!config.SerializedObjects.Add(obj)) + return "Already serialized!" + Environment.NewLine; var type = obj.GetType(); var finalTypes = new[] { @@ -23,15 +26,40 @@ public string Serialize(object obj, int nestingLevel, PrintingConfig confi }; if (finalTypes.Contains(type)) { - return obj + Environment.NewLine; + var result = obj.ToString(); + if (config.CustomCultureInfos.ContainsKey(type)) + result = (obj as IFormattable).ToString(null, config.CustomCultureInfos[type]); + return result + Environment.NewLine; } + var identation = new string('\t', nestingLevel + 1); var sb = new StringBuilder(); sb.AppendLine(type.Name); var properties = type.GetProperties() - .Where(x => !config.ExcludedTypes.Contains(x.PropertyType)); + .Where(x => !config.ExcludedTypes.Contains(x.PropertyType) + && !config.ExcludedProperties.Contains(x.Name)); foreach (var propertyInfo in properties) { + if (config.IsPropertyNeedsUniqueSerialization(propertyInfo.Name, out var serializer)) + { + sb.Append(identation + propertyInfo.Name + " = " + + serializer.Serialize(propertyInfo.GetValue(obj), + nestingLevel + 1, config)); + continue; + } + if (config.IsPropertyNeedsTrim(propertyInfo.Name, out var trimmLen)) + { + var serializedInfo = identation + propertyInfo.Name + " = " + + config.GetSerializerForType(propertyInfo.PropertyType).Serialize( + propertyInfo.GetValue(obj), + nestingLevel + 1, config).Substring(0, trimmLen); + if (serializedInfo[^1] == '\n') + sb.Append(serializedInfo); + else + sb.Append(serializedInfo + '\r' + '\n'); + continue; + } + sb.Append(identation + propertyInfo.Name + " = " + config.GetSerializerForType(propertyInfo.PropertyType).Serialize(propertyInfo.GetValue(obj), nestingLevel + 1, config)); @@ -48,10 +76,18 @@ public string Serialize(object obj, int nestingLevel, PrintingConfig confi } } -public class MyItem : ISerializer +public class PhoneSerializer : ISerializer +{ + public string Serialize(object obj, int nestingLevel, PrintingConfig config) + { + return "my phone" + Environment.NewLine; + } +} + +public class NameSerializer : ISerializer { public string Serialize(object obj, int nestingLevel, PrintingConfig config) { - return "inner serializer" + Environment.NewLine; + return "my name" + Environment.NewLine; } } \ No newline at end of file diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs index 6dbad956f..30e2c2467 100644 --- a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs +++ b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs @@ -1,4 +1,6 @@ using System; +using System.Globalization; +using FluentAssertions; using NUnit.Framework; namespace ObjectPrinting.Tests @@ -7,44 +9,149 @@ namespace ObjectPrinting.Tests public class ObjectPrinterAcceptanceTests { [Test] - public void Demo() + public void DemonstrationTest() { - var person = new Person { Name = "Alex", Age = 19 }; + var person = new Person { Name = "Alex", Surname = "Smith", Age = 19, Height = 180.5}; + person.Phone = new Phone {Name = "Смартфон Vivo", Owner = person}; var printer = ObjectPrinter.For() - .ExcludePropertyOfType(); - //.WithTypeSerializtionType(new PersonSerializer()) - // .SetDigitsCulture() - // .WtihPropertySerializationType(property_name) - // .TrimString() - // .ExcludeProperty(property_name); //1. Исключить из сериализации свойства определенного типа + .ExcludePropertyOfType() //2. Указать альтернативный способ сериализации для определенного типа + .WithTypeSerializtionType(new PhoneSerializer()) //3. Для числовых типов указать культуру + .SetTypeCulture(CultureInfo.InvariantCulture) //4. Настроить сериализацию конкретного свойства + .GetProperty(x => x.Name).SetSerializationType(new NameSerializer()) //5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств) - //6. Исключить из сериализации конкретного свойства + .GetProperty(x => x.Surname).TrimStringToLength(3) + //6. Исключить из сериализации конкретное свойства + .GetProperty(x => x.Id).Exclude(); string s1 = printer.PrintToString(person); Console.WriteLine(s1); //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию + var s2 = person.PrintToString(); + Console.WriteLine("\n\n\n" + s2); //8. ...с конфигурированием + var s3 = person.PrintToString(x => x.ExcludePropertyOfType()); + Console.WriteLine("\n\n\n" + s3); } [Test] - public void ClassWithNestedClassMember() + public void ExcludeTypeTest() { - var person = new Person { Name = "Alex", Age = 19 }; - var test = new TestClass { Id = Guid.NewGuid(), Name = "AlexTest" }; - person.TestClass = test; - var person2 = new Person { Name = "Alexxxxxxxxxxxx", Age = 21 }; - test.Person = person2; - var printer = ObjectPrinter.For() - .ExcludePropertyOfType(); - //.WithTypeSerializtionType(new MyItem()); - string s1 = printer.PrintToString(person); - Console.WriteLine(s1); + var expected = "Person\r\n\tName = Alex\r\n\tSurname = Smith\r\n"; + var person = new Person {Name = "Alex", Surname = "Smith", Age = 19, Height = 180.5}; + var printer = ObjectPrinter + .For() + .ExcludePropertyOfType() + .ExcludePropertyOfType() + .ExcludePropertyOfType() + .ExcludePropertyOfType(); + var ans = printer.PrintToString(person); + ans.Should().Be(expected); + } + + [Test] + public void UniqueTypeSerializationTest() + { + var expected = "Person\r\n\tId = 999999999\r\n\tName = Alex\r\n\tSurname = Smith\r\n\tHeight = 180,5\r\n\tAge = 19\r\n\tPhone = my phone\r\n"; + var person = new Person {Name = "Alex", Surname = "Smith", Age = 19, Height = 180.5}; + person.Phone = new Phone {Name = "Смартфон Vivo"}; + var printer = ObjectPrinter + .For() + .WithTypeSerializtionType(new GuidSerializer()) + .WithTypeSerializtionType(new PhoneSerializer()); + var ans = printer.PrintToString(person); + ans.Should().Be(expected); + } + + [Test] + public void UniqueTypeCultureTest() + { + var expected = "Person\r\n\tId = Guid\r\n\tName = Alex\r\n\tSurname = Smith\r\n\tHeight = 180,5\r\n\tAge = 19\r\n\tPhone = null\r\n"; + var person = new Person {Name = "Alex", Surname = "Smith", Age = 19, Height = 180.5}; + var printer = ObjectPrinter + .For() + .SetTypeCulture(CultureInfo.CurrentCulture); + var ans = printer.PrintToString(person); + ans.Should().Be(expected); + } + + [Test] + public void GetPropertyTest() + { + var expected = "Id"; + var person = new Person {Name = "Alex", Surname = "Smith", Age = 19, Height = 180.5}; + var printer = ObjectPrinter + .For() + .GetProperty(x => x.Id); + var ans = printer.PropertyName; + ans.Should().Be(expected); + } + + [Test] + public void SetPropertySerializationType() + { + var expected = "Person\r\n\tId = Guid\r\n\tName = my name\r\n\tSurname = Smith\r\n\tHeight = 180,5\r\n\tAge = 19\r\n\tPhone = null\r\n"; + var person = new Person {Name = "Alex", Surname = "Smith", Age = 19, Height = 180.5}; + var printer = ObjectPrinter + .For() + .GetProperty(x => x.Name).SetSerializationType(new NameSerializer()); + var ans = printer.PrintToString(person); + ans.Should().Be(expected); + } + + [Test] + public void SetPropertyTrimTest() + { + var expected = "Person\r\n\tId = Guid\r\n\tName = Alex\r\n\tSurname = Very \r\n\tHeight = 180,5\r\n\tAge = 19\r\n\tPhone = null\r\n"; + var person = new Person {Name = "Alex", Surname = "Very long surname", Age = 19, Height = 180.5}; + var printer = ObjectPrinter + .For() + .GetProperty(x => x.Surname).TrimStringToLength(5); + var ans = printer.PrintToString(person); + ans.Should().Be(expected); + } + + [Test] + public void ExcludePropertyTest() + { + var expected = "Person\r\n\tId = Guid\r\n\tName = Alex\r\n\tSurname = Smith\r\n\tHeight = 180,5\r\n\tAge = 19\r\n"; + var person = new Person {Name = "Alex", Surname = "Smith", Age = 19, Height = 180.5}; + var printer = ObjectPrinter + .For() + .GetProperty(x => x.Phone).Exclude(); + var ans = printer.PrintToString(person); + ans.Should().Be(expected); + } + + [Test] + public void LoopReferencesTest() + { + var expected = "Person\r\n\tId = Guid\r\n\tName = Alex\r\n\tSurname = Smith\r\n\tHeight = 180,5" + + "\r\n\tAge = 19\r\n\tPhone = Phone\r\n\t\tId = Guid\r\n\t\t" + + "Name = My phone\r\n\t\tOwner = Already serialized!\r\n"; + var person = new Person {Name = "Alex", Surname = "Smith", Age = 19, Height = 180.5}; + var phone = new Phone {Name = "My phone", Owner = person}; + person.Phone = phone; + var printer = ObjectPrinter + .For(); + var ans = printer.PrintToString(person); + ans.Should().Be(expected); + } + + [Test] + public void ArraySerializationTest() + { + var array = new int[]{1, 2, 3}; + var expected = array.ToString(); + var printer = ObjectPrinter + .For(); + var ans = printer.PrintToString(array); + ans.Should().Be(expected); } } } \ No newline at end of file diff --git a/ObjectPrinting/Tests/Person.cs b/ObjectPrinting/Tests/Person.cs index 7a1c6f1dc..d49cb581f 100644 --- a/ObjectPrinting/Tests/Person.cs +++ b/ObjectPrinting/Tests/Person.cs @@ -6,15 +6,16 @@ public class Person { public Guid Id { get; set; } public string Name { get; set; } + public string Surname { get; set; } public double Height { get; set; } public int Age { get; set; } - public TestClass TestClass { get; set; } + public Phone Phone { get; set; } } - public class TestClass + public class Phone { public Guid Id { get; set; } public string Name { get; set; } - public Person Person { get; set; } + public Person Owner { get; set; } } } \ No newline at end of file From b9b598339afd01e2d29e021a203c3ab1970cb820 Mon Sep 17 00:00:00 2001 From: Foma Medvedev Date: Wed, 19 Nov 2025 12:09:06 +0500 Subject: [PATCH 3/7] =?UTF-8?q?=D0=9C=D0=B8=D0=BD=D0=B8-=D0=BA=D0=BE=D0=BC?= =?UTF-8?q?=D0=BC=D0=B8=D1=82=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B4=20=D0=B2?= =?UTF-8?q?=D0=BE=D0=B7=D0=B2=D1=80=D0=B0=D1=82=D0=BE=D0=BC=20=D0=BB=D0=BE?= =?UTF-8?q?=D0=B3=D0=B8=D0=BA=D0=B8=20=D0=B8=D0=B7=20Serializer.cs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ObjectPrinting/PrintingConfig.cs | 141 ++++++++++++++++--------------- 1 file changed, 71 insertions(+), 70 deletions(-) diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index 2077f9eb1..6fa55b2f1 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -7,17 +7,18 @@ namespace ObjectPrinting { public class PrintingConfig { - public readonly HashSet ExcludedTypes; - public readonly Dictionary TypeSerializers; - public readonly HashSet SerializedObjects; - public readonly Dictionary CustomCultureInfos; - public readonly Dictionary PropertySerializers; - public readonly Dictionary MaxStringLength; - public readonly HashSet ExcludedProperties; + private readonly IReadOnlySet excludedTypes; + private readonly IReadOnlyDictionary typeSerializers; + private readonly IReadOnlySet serializedObjects; + private readonly IReadOnlyDictionary customCultureInfos; + private readonly IReadOnlyDictionary propertySerializers; + private readonly IReadOnlyDictionary maxStringLength; + private readonly IReadOnlySet excludedProperties; + public PrintingConfig() : this( new HashSet(), new Dictionary(), - new (ReferenceEqualityComparer.Instance), + new HashSet(ReferenceEqualityComparer.Instance), new Dictionary(), new Dictionary(), new Dictionary(), @@ -25,99 +26,99 @@ public PrintingConfig() : this( {} private PrintingConfig( - HashSet excludedTypes, - Dictionary typeSerializers, - HashSet serializedObjects, - Dictionary customCultureInfos, - Dictionary propertySerializers, - Dictionary maxStringLength, - HashSet excludedProperties) + IReadOnlySet excludedTypes, + IReadOnlyDictionary typeSerializers, + IReadOnlySet serializedObjects, + IReadOnlyDictionary customCultureInfos, + IReadOnlyDictionary propertySerializers, + IReadOnlyDictionary maxStringLength, + IReadOnlySet excludedProperties) { - this.ExcludedTypes = excludedTypes; - this.TypeSerializers = typeSerializers; - this.SerializedObjects = serializedObjects; - this.CustomCultureInfos = customCultureInfos; - this.PropertySerializers = propertySerializers; - this.MaxStringLength = maxStringLength; - this.ExcludedProperties = excludedProperties; + this.excludedTypes = excludedTypes; + this.typeSerializers = typeSerializers; + this.serializedObjects = serializedObjects; + this.customCultureInfos = customCultureInfos; + this.propertySerializers = propertySerializers; + this.maxStringLength = maxStringLength; + this.excludedProperties = excludedProperties; } public PrintingConfig ExcludePropertyOfType() { - var tempSet = new HashSet(ExcludedTypes); + var tempSet = new HashSet(excludedTypes); tempSet.Add(typeof(T)); return new PrintingConfig(tempSet, - TypeSerializers, - SerializedObjects, - CustomCultureInfos, - PropertySerializers, - MaxStringLength, - ExcludedProperties); + typeSerializers, + serializedObjects, + customCultureInfos, + propertySerializers, + maxStringLength, + excludedProperties); } public PrintingConfig WithTypeSerializtionType(ISerializer serializer) { - var tempDict = new Dictionary(TypeSerializers); + var tempDict = new Dictionary(typeSerializers); tempDict.Add(typeof(T), serializer); - return new PrintingConfig(ExcludedTypes, + return new PrintingConfig(excludedTypes, tempDict, - SerializedObjects, - CustomCultureInfos, - PropertySerializers, - MaxStringLength, - ExcludedProperties); + serializedObjects, + customCultureInfos, + propertySerializers, + maxStringLength, + excludedProperties); } public PrintingConfig SetTypeCulture(CultureInfo cultureInfo) where T : IFormattable { - var tempDict = new Dictionary(CustomCultureInfos); + var tempDict = new Dictionary(customCultureInfos); tempDict.Add(typeof(T), cultureInfo); - return new PrintingConfig(ExcludedTypes, - TypeSerializers, - SerializedObjects, + return new PrintingConfig(excludedTypes, + typeSerializers, + serializedObjects, tempDict, - PropertySerializers, - MaxStringLength, - ExcludedProperties); + propertySerializers, + maxStringLength, + excludedProperties); } public PrintingConfig WithPropertySerialization(string propertyName, ISerializer serializer) { - var tempDict = new Dictionary(PropertySerializers); + var tempDict = new Dictionary(propertySerializers); tempDict.Add(propertyName, serializer); - return new PrintingConfig(ExcludedTypes, - TypeSerializers, - SerializedObjects, - CustomCultureInfos, + return new PrintingConfig(excludedTypes, + typeSerializers, + serializedObjects, + customCultureInfos, tempDict, - MaxStringLength, - ExcludedProperties); + maxStringLength, + excludedProperties); } public PrintingConfig TrimStringToLength(string propertyName, int length) { - var tempDict = new Dictionary(MaxStringLength); + var tempDict = new Dictionary(maxStringLength); tempDict.Add(propertyName, length); - return new PrintingConfig(ExcludedTypes, - TypeSerializers, - SerializedObjects, - CustomCultureInfos, - PropertySerializers, + return new PrintingConfig(excludedTypes, + typeSerializers, + serializedObjects, + customCultureInfos, + propertySerializers, tempDict, - ExcludedProperties); + excludedProperties); } public PrintingConfig Exclude(string propertyName) { - var tempSet = new HashSet(ExcludedProperties); + var tempSet = new HashSet(excludedProperties); tempSet.Add(propertyName); - return new PrintingConfig(ExcludedTypes, - TypeSerializers, - SerializedObjects, - CustomCultureInfos, - PropertySerializers, - MaxStringLength, + return new PrintingConfig(excludedTypes, + typeSerializers, + serializedObjects, + customCultureInfos, + propertySerializers, + maxStringLength, tempSet); } @@ -150,17 +151,17 @@ private string PrintToString(object obj, int nestingLevel) public ISerializer GetSerializerForType(Type type) { - if (TypeSerializers.ContainsKey(type)) - return TypeSerializers[type]; + if (typeSerializers.ContainsKey(type)) + return typeSerializers[type]; return new Serializer(); } public bool IsPropertyNeedsUniqueSerialization(string propertyName, out ISerializer serializer) { serializer = null; - if (PropertySerializers.ContainsKey(propertyName)) + if (propertySerializers.ContainsKey(propertyName)) { - serializer = PropertySerializers[propertyName]; + serializer = propertySerializers[propertyName]; return true; } return false; @@ -169,9 +170,9 @@ public bool IsPropertyNeedsUniqueSerialization(string propertyName, out ISeriali public bool IsPropertyNeedsTrim(string propertyName, out int trimLength) { trimLength = 0; - if (MaxStringLength.ContainsKey(propertyName)) + if (maxStringLength.ContainsKey(propertyName)) { - trimLength = MaxStringLength[propertyName]; + trimLength = maxStringLength[propertyName]; return true; } return false; From a2bf07c1e1d957a60ad7a91b553f7c3bf9b856b1 Mon Sep 17 00:00:00 2001 From: Foma Medvedev Date: Wed, 19 Nov 2025 17:38:03 +0500 Subject: [PATCH 4/7] =?UTF-8?q?=D0=9E=D1=82=D1=80=D0=B5=D1=84=D0=B0=D0=BA?= =?UTF-8?q?=D1=82=D0=BE=D1=80=D0=B8=D0=BB=20=D0=BF=D0=BE=20=D0=B7=D0=B0?= =?UTF-8?q?=D0=BC=D0=B5=D1=87=D0=B0=D0=BD=D0=B8=D1=8F=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ObjectPrinting/PrintingConfig.cs | 179 ++++++++++++++---- ObjectPrinting/PropertyPrintingConfig.cs | 22 ++- .../PropertyPrintingConfigExtensions.cs | 7 +- ObjectPrinting/Serializers.cs | 82 +------- .../Tests/ObjectPrinterAcceptanceTests.cs | 113 ++++++++--- 5 files changed, 249 insertions(+), 154 deletions(-) diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index 6fa55b2f1..fabe38beb 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -1,36 +1,53 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.Linq; using System.Linq.Expressions; +using System.Reflection; +using System.Text; namespace ObjectPrinting { public class PrintingConfig { private readonly IReadOnlySet excludedTypes; - private readonly IReadOnlyDictionary typeSerializers; - private readonly IReadOnlySet serializedObjects; + private readonly IReadOnlyDictionary typeSerializers; + private readonly HashSet serializedObjects; private readonly IReadOnlyDictionary customCultureInfos; - private readonly IReadOnlyDictionary propertySerializers; + private readonly IReadOnlyDictionary propertySerializers; private readonly IReadOnlyDictionary maxStringLength; private readonly IReadOnlySet excludedProperties; + private const int MaxDeepnessLevel = 2; + private const char Tabchar = '\t'; + private const char ResetCaretChar = '\r'; + private const char NewLineChar = '\n'; + private readonly string environmentNewLine = Environment.NewLine; + private const string EqualityWithSpaces = " = "; + + private static HashSet _finalTypes = + [ + typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint), + typeof(long), typeof(ulong), typeof(float), typeof(double), typeof(double), typeof(string), typeof(bool), + typeof(decimal), typeof(char), typeof(string), typeof(Guid), + typeof(DateTime), typeof(TimeSpan) + ]; public PrintingConfig() : this( new HashSet(), - new Dictionary(), + new Dictionary(), new HashSet(ReferenceEqualityComparer.Instance), new Dictionary(), - new Dictionary(), + new Dictionary(), new Dictionary(), new HashSet()) {} private PrintingConfig( IReadOnlySet excludedTypes, - IReadOnlyDictionary typeSerializers, - IReadOnlySet serializedObjects, + IReadOnlyDictionary typeSerializers, + HashSet serializedObjects, IReadOnlyDictionary customCultureInfos, - IReadOnlyDictionary propertySerializers, + IReadOnlyDictionary propertySerializers, IReadOnlyDictionary maxStringLength, IReadOnlySet excludedProperties) { @@ -45,8 +62,9 @@ private PrintingConfig( public PrintingConfig ExcludePropertyOfType() { - var tempSet = new HashSet(excludedTypes); - tempSet.Add(typeof(T)); + if (excludedTypes.Contains(typeof(T))) + throw new InvalidOperationException($"The type {typeof(T)} is already excluded."); + var tempSet = new HashSet(excludedTypes) { typeof(T) }; return new PrintingConfig(tempSet, typeSerializers, serializedObjects, @@ -56,10 +74,11 @@ public PrintingConfig ExcludePropertyOfType() excludedProperties); } - public PrintingConfig WithTypeSerializtionType(ISerializer serializer) + public PrintingConfig WithTypeSerializationStyle(Serializer serializer) { - var tempDict = new Dictionary(typeSerializers); - tempDict.Add(typeof(T), serializer); + var tempDict = new Dictionary(typeSerializers); + if (!tempDict.TryAdd(typeof(T), serializer)) + throw new InvalidOperationException($"Type {typeof(T).FullName} has already been set serialization type"); return new PrintingConfig(excludedTypes, tempDict, serializedObjects, @@ -73,7 +92,8 @@ public PrintingConfig SetTypeCulture(CultureInfo cultureInfo) where T : IFormattable { var tempDict = new Dictionary(customCultureInfos); - tempDict.Add(typeof(T), cultureInfo); + if (!tempDict.TryAdd(typeof(T), cultureInfo)) + throw new InvalidOperationException($"Type {typeof(T).FullName} has already been set culture info"); return new PrintingConfig(excludedTypes, typeSerializers, serializedObjects, @@ -83,10 +103,11 @@ public PrintingConfig SetTypeCulture(CultureInfo cultureInfo) excludedProperties); } - public PrintingConfig WithPropertySerialization(string propertyName, ISerializer serializer) + public PrintingConfig WithPropertySerialization(string propertyName, Serializer serializer) { - var tempDict = new Dictionary(propertySerializers); - tempDict.Add(propertyName, serializer); + var tempDict = new Dictionary(propertySerializers); + if (!tempDict.TryAdd(propertyName, serializer)) + throw new InvalidOperationException($"Property {propertyName} has already been set serialization type"); return new PrintingConfig(excludedTypes, typeSerializers, serializedObjects, @@ -96,10 +117,12 @@ public PrintingConfig WithPropertySerialization(string propertyName, ISe excludedProperties); } - public PrintingConfig TrimStringToLength(string propertyName, int length) + public PrintingConfig TrimStringToLength(Expression> propertyExpr, int length) { + var member = (MemberExpression)propertyExpr.Body; var tempDict = new Dictionary(maxStringLength); - tempDict.Add(propertyName, length); + if (!tempDict.TryAdd(member.Member.Name, length)) + throw new InvalidOperationException($"Property {member.Member.Name} has already been trim length"); return new PrintingConfig(excludedTypes, typeSerializers, serializedObjects, @@ -112,7 +135,8 @@ public PrintingConfig TrimStringToLength(string propertyName, int length public PrintingConfig Exclude(string propertyName) { var tempSet = new HashSet(excludedProperties); - tempSet.Add(propertyName); + if (!tempSet.Add(propertyName)) + throw new InvalidOperationException($"Property {propertyName} has already been excluded"); return new PrintingConfig(excludedTypes, typeSerializers, serializedObjects, @@ -122,13 +146,7 @@ public PrintingConfig Exclude(string propertyName) tempSet); } - public PropertyPrintingConfig GetProperty( - Expression> targetProperty) - { - return new PropertyPrintingConfig(this, targetProperty); - } - - public PropertyPrintingConfig Exclude( + public PropertyPrintingConfig GetPropertyByName( Expression> targetProperty) { return new PropertyPrintingConfig(this, targetProperty); @@ -139,40 +157,121 @@ public string PrintToString(TOwner obj) return PrintToString(obj, 0); } - private string PrintToString(object obj, int nestingLevel) + private string PrintToString(object? obj, int nestingLevel) { //TODO apply configurations if (obj is null) - return "null" + Environment.NewLine; + return "null" + environmentNewLine; var type = obj.GetType(); - return GetSerializerForType(type).Serialize(obj, nestingLevel, this); + return GetSerializerForType(type).SerializerFunc.Invoke(obj, nestingLevel, 0); + } + + private string Serialize(object? obj, int nestingLevel, int deepnessLevel) + { + if (obj is null) + return "null" + environmentNewLine; + var type = obj.GetType(); + if (TrySerializeAsBaseFields(obj, type, out var serialized) && serialized is not null) + return serialized; + + var sb = new StringBuilder(); + sb.Append(SerializeComplexField(type, deepnessLevel, obj, nestingLevel)); + return sb.ToString(); + } + + private bool TrySerializeAsBaseFields(object obj, Type type, out string? result) + { + result = null; + if (customCultureInfos.TryGetValue(type, out var info)) + { + result = obj.ToString(); + result = (obj as IFormattable)?.ToString(null, info) + environmentNewLine; + return true; + } + if (_finalTypes.Contains(type)) + { + result = obj.ToString() + environmentNewLine; + return true; + } + return false; + } + + private string SerializeComplexField(Type type, int deepnessLevel, object obj, int nestingLevel) + { + var sb = new StringBuilder(); + sb.AppendLine(type.Name); + var properties = type.GetProperties() + .Where(x => !excludedTypes.Contains(x.PropertyType) + && !excludedProperties.Contains(x.Name)); + foreach (var propertyInfo in properties) + { + if (deepnessLevel == MaxDeepnessLevel) + return $"Deepness exceeded!" + environmentNewLine; + sb.Append(SerializeProperty(propertyInfo, obj, nestingLevel, deepnessLevel)); + } + return sb.ToString(); + } + + private string SerializeProperty(PropertyInfo propertyInfo, object obj, int nestingLevel, int deepnessLevel) + { + var sb = new StringBuilder(); + if (IsPropertyNeedsUniqueSerialization(propertyInfo.Name, out var serializer)) + { + sb.Append(BuildSerializedField(propertyInfo, serializer, obj, nestingLevel, deepnessLevel)); + return sb.ToString(); + } + if (IsPropertyNeedsTrim(propertyInfo.Name, out var trimLen)) + { + var identation = new string(Tabchar, nestingLevel + 1); + var serializedInfo = BuildSerializedField(propertyInfo, null, obj, nestingLevel, deepnessLevel); + var dataPrefixLength = identation.Length + propertyInfo.Name.Length + EqualityWithSpaces.Length; + serializedInfo = serializedInfo.Substring(0, dataPrefixLength + trimLen) + .TrimEnd(new char[] {ResetCaretChar, NewLineChar, Tabchar}); + + sb.Append(serializedInfo + ResetCaretChar + NewLineChar); + return sb.ToString(); + } + sb.Append(BuildSerializedField(propertyInfo, null, obj, nestingLevel, deepnessLevel)); + return sb.ToString(); + } + + private string BuildSerializedField(PropertyInfo propertyInfo, Serializer? serializer, + object? obj, int nestingLevel, int deepnessLevel) + { + var identation = new string(Tabchar, nestingLevel + 1); + var sb = new StringBuilder(); + sb.Append(identation + propertyInfo.Name + EqualityWithSpaces); + if (serializer is null) + serializer = GetSerializerForType(propertyInfo.PropertyType); + sb.Append(serializer.SerializerFunc.Invoke(propertyInfo.GetValue(obj), nestingLevel + 1, deepnessLevel + 1)); + return sb.ToString(); } - public ISerializer GetSerializerForType(Type type) + private Serializer GetSerializerForType(Type type) { - if (typeSerializers.ContainsKey(type)) - return typeSerializers[type]; - return new Serializer(); + if (typeSerializers.TryGetValue(type, out var forType)) + return forType; + return new Serializer(Serialize); } - public bool IsPropertyNeedsUniqueSerialization(string propertyName, out ISerializer serializer) + private bool IsPropertyNeedsUniqueSerialization(string propertyName, out Serializer? serializer) { serializer = null; - if (propertySerializers.ContainsKey(propertyName)) + if (propertySerializers.TryGetValue(propertyName, out var propertySerializer)) { - serializer = propertySerializers[propertyName]; + serializer = propertySerializer; return true; } return false; } - public bool IsPropertyNeedsTrim(string propertyName, out int trimLength) + private bool IsPropertyNeedsTrim(string propertyName, out int trimLength) { trimLength = 0; - if (maxStringLength.ContainsKey(propertyName)) + if (maxStringLength.TryGetValue(propertyName, out var value)) { - trimLength = maxStringLength[propertyName]; + trimLength = value; return true; } return false; diff --git a/ObjectPrinting/PropertyPrintingConfig.cs b/ObjectPrinting/PropertyPrintingConfig.cs index b20e3d5ad..fbe60aea5 100644 --- a/ObjectPrinting/PropertyPrintingConfig.cs +++ b/ObjectPrinting/PropertyPrintingConfig.cs @@ -5,16 +5,26 @@ namespace ObjectPrinting; public class PropertyPrintingConfig { - private readonly PrintingConfig _config; - private Expression>? _propertySelector; + private readonly PrintingConfig config; + private readonly Expression>? propertySelector; public PropertyPrintingConfig(PrintingConfig config, Expression>? propertySelector) { - this._config = config; - this._propertySelector = propertySelector; + this.config = config; + this.propertySelector = ValidateExpression(propertySelector); + } + + private Expression> ValidateExpression(Expression>? propertySelector) + { + if (propertySelector == null) + throw new ArgumentNullException(nameof(propertySelector)); + if (propertySelector.Body is not MemberExpression memberExpression) + throw new ArgumentException($"{nameof(propertySelector)} must select property"); + return propertySelector; } - public PrintingConfig ParentConfig => _config; + public PrintingConfig ParentConfig => config; + public Expression>? PropertySelector => propertySelector; - public string PropertyName => ((MemberExpression)_propertySelector.Body).Member.Name; + public string PropertyName => ((MemberExpression)propertySelector!.Body).Member.Name; } \ No newline at end of file diff --git a/ObjectPrinting/PropertyPrintingConfigExtensions.cs b/ObjectPrinting/PropertyPrintingConfigExtensions.cs index 5d3a8af87..284a98902 100644 --- a/ObjectPrinting/PropertyPrintingConfigExtensions.cs +++ b/ObjectPrinting/PropertyPrintingConfigExtensions.cs @@ -2,8 +2,8 @@ namespace ObjectPrinting; public static class PropertyPrintingConfigExtensions { - public static PrintingConfig SetSerializationType - (this PropertyPrintingConfig propertyConfig, ISerializer serializer) + public static PrintingConfig SetSerializationStyle + (this PropertyPrintingConfig propertyConfig, Serializer serializer) { var propertyName = propertyConfig.PropertyName; return propertyConfig.ParentConfig.WithPropertySerialization(propertyName, serializer); @@ -12,8 +12,7 @@ public static PrintingConfig SetSerializationType public static PrintingConfig TrimStringToLength (this PropertyPrintingConfig propertyConfig, int length) { - var propertyName = propertyConfig.PropertyName; - return propertyConfig.ParentConfig.TrimStringToLength(propertyName, length); + return propertyConfig.ParentConfig.TrimStringToLength(propertyConfig.PropertySelector!, length); } public static PrintingConfig Exclude diff --git a/ObjectPrinting/Serializers.cs b/ObjectPrinting/Serializers.cs index 0c5a0187a..9ca82a4c3 100644 --- a/ObjectPrinting/Serializers.cs +++ b/ObjectPrinting/Serializers.cs @@ -7,87 +7,17 @@ namespace ObjectPrinting; public interface ISerializer { - public string Serialize(object obj, int nestingLevel, PrintingConfig config); + public Func SerializerFunc { get; } + public Func GetSerializerFunc => SerializerFunc; } public class Serializer : ISerializer { - public string Serialize(object obj, int nestingLevel, PrintingConfig config) - { - if (obj is null) - return "null" + Environment.NewLine; - if (!config.SerializedObjects.Add(obj)) - return "Already serialized!" + Environment.NewLine; - var type = obj.GetType(); - var finalTypes = new[] - { - typeof(int), typeof(double), typeof(float), typeof(string), - typeof(DateTime), typeof(TimeSpan) - }; - if (finalTypes.Contains(type)) - { - var result = obj.ToString(); - if (config.CustomCultureInfos.ContainsKey(type)) - result = (obj as IFormattable).ToString(null, config.CustomCultureInfos[type]); - return result + Environment.NewLine; - } - - var identation = new string('\t', nestingLevel + 1); - var sb = new StringBuilder(); - sb.AppendLine(type.Name); - var properties = type.GetProperties() - .Where(x => !config.ExcludedTypes.Contains(x.PropertyType) - && !config.ExcludedProperties.Contains(x.Name)); - foreach (var propertyInfo in properties) - { - if (config.IsPropertyNeedsUniqueSerialization(propertyInfo.Name, out var serializer)) - { - sb.Append(identation + propertyInfo.Name + " = " + - serializer.Serialize(propertyInfo.GetValue(obj), - nestingLevel + 1, config)); - continue; - } - if (config.IsPropertyNeedsTrim(propertyInfo.Name, out var trimmLen)) - { - var serializedInfo = identation + propertyInfo.Name + " = " + - config.GetSerializerForType(propertyInfo.PropertyType).Serialize( - propertyInfo.GetValue(obj), - nestingLevel + 1, config).Substring(0, trimmLen); - if (serializedInfo[^1] == '\n') - sb.Append(serializedInfo); - else - sb.Append(serializedInfo + '\r' + '\n'); - continue; - } - - sb.Append(identation + propertyInfo.Name + " = " + - config.GetSerializerForType(propertyInfo.PropertyType).Serialize(propertyInfo.GetValue(obj), - nestingLevel + 1, config)); - } - return sb.ToString(); - } -} - -public class GuidSerializer : ISerializer -{ - public string Serialize(object obj, int nestingLevel, PrintingConfig config) - { - return "999999999" + Environment.NewLine; - } -} - -public class PhoneSerializer : ISerializer -{ - public string Serialize(object obj, int nestingLevel, PrintingConfig config) - { - return "my phone" + Environment.NewLine; - } -} + public Func SerializerFunc { get; } + public Func GetSerializeFunc => SerializerFunc; -public class NameSerializer : ISerializer -{ - public string Serialize(object obj, int nestingLevel, PrintingConfig config) + public Serializer(Func serializerFunc) { - return "my name" + Environment.NewLine; + this.SerializerFunc = serializerFunc; } } \ No newline at end of file diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs index 30e2c2467..59934f8af 100644 --- a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs +++ b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs @@ -18,15 +18,16 @@ public void DemonstrationTest() //1. Исключить из сериализации свойства определенного типа .ExcludePropertyOfType() //2. Указать альтернативный способ сериализации для определенного типа - .WithTypeSerializtionType(new PhoneSerializer()) + .WithTypeSerializationStyle(new Serializer((obj, nestingLevel, deepnessLevel) => "MyPhone" + Environment.NewLine)) //3. Для числовых типов указать культуру .SetTypeCulture(CultureInfo.InvariantCulture) //4. Настроить сериализацию конкретного свойства - .GetProperty(x => x.Name).SetSerializationType(new NameSerializer()) + .GetPropertyByName(x => x.Name) + .SetSerializationStyle(new Serializer((obj, nestingLevel, deepnessLevel) => "MyName" + Environment.NewLine)) //5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств) - .GetProperty(x => x.Surname).TrimStringToLength(3) + .GetPropertyByName(x => x.Surname).TrimStringToLength(3) //6. Исключить из сериализации конкретное свойства - .GetProperty(x => x.Id).Exclude(); + .GetPropertyByName(x => x.Id).Exclude(); string s1 = printer.PrintToString(person); Console.WriteLine(s1); @@ -35,14 +36,13 @@ public void DemonstrationTest() var s2 = person.PrintToString(); Console.WriteLine("\n\n\n" + s2); //8. ...с конфигурированием - var s3 = person.PrintToString(x => x.ExcludePropertyOfType()); + var s3 = person.PrintToString(x => x.GetPropertyByName(p => p.Name).TrimStringToLength(3)); Console.WriteLine("\n\n\n" + s3); } [Test] - public void ExcludeTypeTest() + public void ExcludeType_ShouldRemove_PropertiesWithThisType() { - var expected = "Person\r\n\tName = Alex\r\n\tSurname = Smith\r\n"; var person = new Person {Name = "Alex", Surname = "Smith", Age = 19, Height = 180.5}; var printer = ObjectPrinter .For() @@ -50,90 +50,147 @@ public void ExcludeTypeTest() .ExcludePropertyOfType() .ExcludePropertyOfType() .ExcludePropertyOfType(); + + var expected = + "Person\r\n" + + "\tName = Alex\r\n" + + "\tSurname = Smith\r\n"; + var ans = printer.PrintToString(person); ans.Should().Be(expected); } [Test] - public void UniqueTypeSerializationTest() + public void WithTypeSerializationStyle_ShouldSetUniqueSerializationStyle_ToPropertiesWithThisType() { - var expected = "Person\r\n\tId = 999999999\r\n\tName = Alex\r\n\tSurname = Smith\r\n\tHeight = 180,5\r\n\tAge = 19\r\n\tPhone = my phone\r\n"; var person = new Person {Name = "Alex", Surname = "Smith", Age = 19, Height = 180.5}; person.Phone = new Phone {Name = "Смартфон Vivo"}; var printer = ObjectPrinter .For() - .WithTypeSerializtionType(new GuidSerializer()) - .WithTypeSerializtionType(new PhoneSerializer()); + .WithTypeSerializationStyle(new Serializer((obj, nestingLevel, deepnessLevel) => "999999999" + Environment.NewLine)) + .WithTypeSerializationStyle(new Serializer((obj, nestingLevel, deepnessLevel) => "My phone" + Environment.NewLine)); var ans = printer.PrintToString(person); + + var expected = + "Person\r\n\t" + + "Id = 999999999\r\n\t" + + "Name = Alex\r\n\t" + + "Surname = Smith\r\n\t" + + "Height = 180,5\r\n\t" + + "Age = 19\r\n\t" + + "Phone = My phone\r\n"; + ans.Should().Be(expected); } [Test] - public void UniqueTypeCultureTest() + public void SetTypeCulture_ShouldSetTypeCulture_ToPropertiesWithThisType() { - var expected = "Person\r\n\tId = Guid\r\n\tName = Alex\r\n\tSurname = Smith\r\n\tHeight = 180,5\r\n\tAge = 19\r\n\tPhone = null\r\n"; var person = new Person {Name = "Alex", Surname = "Smith", Age = 19, Height = 180.5}; var printer = ObjectPrinter .For() .SetTypeCulture(CultureInfo.CurrentCulture); var ans = printer.PrintToString(person); + + var baseGuid = Guid.Empty.ToString(); + var expected = + "Person\r\n\t" + + $"Id = {baseGuid}\r\n\t" + + "Name = Alex\r\n\t" + + "Surname = Smith\r\n\t" + + "Height = 180,5\r\n\t" + + "Age = 19\r\n\t" + + "Phone = null\r\n"; + ans.Should().Be(expected); } [Test] - public void GetPropertyTest() + public void GetPropertyByName_ShouldReturn_PropertyInfoByName() { - var expected = "Id"; var person = new Person {Name = "Alex", Surname = "Smith", Age = 19, Height = 180.5}; var printer = ObjectPrinter .For() - .GetProperty(x => x.Id); + .GetPropertyByName(x => x.Id); var ans = printer.PropertyName; + + var expected = "Id"; + ans.Should().Be(expected); } [Test] - public void SetPropertySerializationType() + public void SetSerializationStyle_ShouldSetUniqueSerializationStyle_ToGotProperty() { - var expected = "Person\r\n\tId = Guid\r\n\tName = my name\r\n\tSurname = Smith\r\n\tHeight = 180,5\r\n\tAge = 19\r\n\tPhone = null\r\n"; var person = new Person {Name = "Alex", Surname = "Smith", Age = 19, Height = 180.5}; var printer = ObjectPrinter .For() - .GetProperty(x => x.Name).SetSerializationType(new NameSerializer()); + .GetPropertyByName(x => x.Name).SetSerializationStyle(new Serializer((obj, nestingLevel, deepnessLevel) => "My name" + Environment.NewLine)); var ans = printer.PrintToString(person); + + var baseGuid = Guid.Empty.ToString(); + var expected = + "Person\r\n\t" + + $"Id = {baseGuid}\r\n\t" + + "Name = My name\r\n\t" + + "Surname = Smith\r\n\t" + + "Height = 180,5\r\n\t" + + "Age = 19\r\n\t" + + "Phone = null\r\n"; + ans.Should().Be(expected); } [Test] - public void SetPropertyTrimTest() + public void TrimStringToLength_ShouldTrimPropertyLength_ToGotProperty() { - var expected = "Person\r\n\tId = Guid\r\n\tName = Alex\r\n\tSurname = Very \r\n\tHeight = 180,5\r\n\tAge = 19\r\n\tPhone = null\r\n"; var person = new Person {Name = "Alex", Surname = "Very long surname", Age = 19, Height = 180.5}; var printer = ObjectPrinter .For() - .GetProperty(x => x.Surname).TrimStringToLength(5); + .GetPropertyByName(x => x.Surname).TrimStringToLength(5); var ans = printer.PrintToString(person); + + var baseGuid = Guid.Empty.ToString(); + var expected = + "Person\r\n\t" + + $"Id = {baseGuid}\r\n\t" + + "Name = Alex\r\n\t" + + "Surname = Very \r\n\t" + + "Height = 180,5\r\n\t" + + "Age = 19\r\n\t" + + "Phone = null\r\n"; + ans.Should().Be(expected); } [Test] - public void ExcludePropertyTest() + public void Exclude_ShouldRemove_GotProperty() { - var expected = "Person\r\n\tId = Guid\r\n\tName = Alex\r\n\tSurname = Smith\r\n\tHeight = 180,5\r\n\tAge = 19\r\n"; var person = new Person {Name = "Alex", Surname = "Smith", Age = 19, Height = 180.5}; var printer = ObjectPrinter .For() - .GetProperty(x => x.Phone).Exclude(); + .GetPropertyByName(x => x.Phone).Exclude(); var ans = printer.PrintToString(person); + + var baseGuid = Guid.Empty.ToString(); + var expected = + "Person\r\n\t" + + $"Id = {baseGuid}\r\n\t" + + "Name = Alex\r\n\t" + + "Surname = Smith\r\n\t" + + "Height = 180,5\r\n\t" + + "Age = 19\r\n"; + ans.Should().Be(expected); } [Test] public void LoopReferencesTest() { - var expected = "Person\r\n\tId = Guid\r\n\tName = Alex\r\n\tSurname = Smith\r\n\tHeight = 180,5" + - "\r\n\tAge = 19\r\n\tPhone = Phone\r\n\t\tId = Guid\r\n\t\t" + - "Name = My phone\r\n\t\tOwner = Already serialized!\r\n"; + var baseGuid = Guid.Empty.ToString(); + var expected = $"Person\r\n\tId = {baseGuid}\r\n\tName = Alex\r\n\tSurname = Smith\r\n\tHeight = 180,5" + + $"\r\n\tAge = 19\r\n\tPhone = Phone\r\n\t\tId = {baseGuid}\r\n\t\t" + + "Name = My phone\r\n\t\tOwner = Deepness exceeded!\r\n"; var person = new Person {Name = "Alex", Surname = "Smith", Age = 19, Height = 180.5}; var phone = new Phone {Name = "My phone", Owner = person}; person.Phone = phone; From 3cd83772f8efbcec333b36b42e90a2c96c8cda61 Mon Sep 17 00:00:00 2001 From: Foma Medvedev Date: Thu, 20 Nov 2025 14:49:01 +0500 Subject: [PATCH 5/7] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB?= =?UTF-8?q?=20=D1=81=D0=B5=D1=80=D0=B8=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D1=8E=20=D0=B1=D0=B0=D0=B7=D0=BE=D0=B2=D1=8B=D1=85=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=BB=D0=B5=D0=BA=D1=86=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ObjectPrinting/PrintingConfig.cs | 63 +++++++++++++++---- .../Tests/ObjectPrinterAcceptanceTests.cs | 50 ++++++++++++++- 2 files changed, 99 insertions(+), 14 deletions(-) diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index fabe38beb..569f071c0 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -21,8 +22,9 @@ public class PrintingConfig private const char Tabchar = '\t'; private const char ResetCaretChar = '\r'; private const char NewLineChar = '\n'; - private readonly string environmentNewLine = Environment.NewLine; + private static readonly string EnvironmentNewLine = Environment.NewLine; private const string EqualityWithSpaces = " = "; + private readonly string deepnessExceededString = $"Deepness exceeded!{EnvironmentNewLine}"; private static HashSet _finalTypes = [ @@ -161,7 +163,7 @@ private string PrintToString(object? obj, int nestingLevel) { //TODO apply configurations if (obj is null) - return "null" + environmentNewLine; + return "null" + EnvironmentNewLine; var type = obj.GetType(); return GetSerializerForType(type).SerializerFunc.Invoke(obj, nestingLevel, 0); @@ -170,9 +172,9 @@ private string PrintToString(object? obj, int nestingLevel) private string Serialize(object? obj, int nestingLevel, int deepnessLevel) { if (obj is null) - return "null" + environmentNewLine; + return "null" + EnvironmentNewLine; var type = obj.GetType(); - if (TrySerializeAsBaseFields(obj, type, out var serialized) && serialized is not null) + if (TrySerializeAsBaseFields(obj, type, nestingLevel, out var serialized) && serialized is not null) return serialized; var sb = new StringBuilder(); @@ -180,18 +182,17 @@ private string Serialize(object? obj, int nestingLevel, int deepnessLevel) return sb.ToString(); } - private bool TrySerializeAsBaseFields(object obj, Type type, out string? result) + private bool TrySerializeAsBaseFields(object obj, Type type, int nestingLevel, out string? result) { result = null; if (customCultureInfos.TryGetValue(type, out var info)) { - result = obj.ToString(); - result = (obj as IFormattable)?.ToString(null, info) + environmentNewLine; + result = (obj as IFormattable)?.ToString(null, info) + EnvironmentNewLine; return true; } if (_finalTypes.Contains(type)) { - result = obj.ToString() + environmentNewLine; + result = obj.ToString() + EnvironmentNewLine; return true; } return false; @@ -201,18 +202,58 @@ private string SerializeComplexField(Type type, int deepnessLevel, object obj, i { var sb = new StringBuilder(); sb.AppendLine(type.Name); + if (obj is IEnumerable enumerable && type != typeof(string)) + { + return SerializeCollection(enumerable, nestingLevel, deepnessLevel); + } var properties = type.GetProperties() .Where(x => !excludedTypes.Contains(x.PropertyType) && !excludedProperties.Contains(x.Name)); foreach (var propertyInfo in properties) { - if (deepnessLevel == MaxDeepnessLevel) - return $"Deepness exceeded!" + environmentNewLine; + if (IsDeepnessOverflow(deepnessLevel)) + return deepnessExceededString; sb.Append(SerializeProperty(propertyInfo, obj, nestingLevel, deepnessLevel)); } return sb.ToString(); } + private string SerializeCollection(IEnumerable enumerable, int nestingLevel, int deepnessLevel) + { + var sb = new StringBuilder(); + sb.AppendLine(enumerable.GetType().Name); + sb.Append(new string(Tabchar, nestingLevel + 1)); + foreach (var obj in enumerable) + { + var objType = obj.GetType(); + var isKeyValuePair = objType.IsGenericType + && objType.GetGenericTypeDefinition() == typeof(KeyValuePair<,>); + if (isKeyValuePair) + sb.Append(SerializeDictionaryElement(obj, deepnessLevel + 1)); + else + sb.Append(GetSerializerForType(obj.GetType()).SerializerFunc + .Invoke(obj, nestingLevel + 1, deepnessLevel + 1)); + sb.Append(new string(Tabchar, nestingLevel + 1)); + } + sb.Remove(sb.Length - 1, 1); + return sb.ToString(); + } + + private string SerializeDictionaryElement(object obj, int deepnessLevel) + { + if (IsDeepnessOverflow(deepnessLevel)) + return deepnessExceededString; + var type = obj.GetType(); + var key = type.GetProperty("Key")!.GetValue(obj); + var value = type.GetProperty("Value")!.GetValue(obj); + return new string(key + " = " + value + EnvironmentNewLine); + } + + private bool IsDeepnessOverflow(int deepnessLevel) + { + return deepnessLevel >= MaxDeepnessLevel; + } + private string SerializeProperty(PropertyInfo propertyInfo, object obj, int nestingLevel, int deepnessLevel) { var sb = new StringBuilder(); @@ -227,7 +268,7 @@ private string SerializeProperty(PropertyInfo propertyInfo, object obj, int nest var serializedInfo = BuildSerializedField(propertyInfo, null, obj, nestingLevel, deepnessLevel); var dataPrefixLength = identation.Length + propertyInfo.Name.Length + EqualityWithSpaces.Length; serializedInfo = serializedInfo.Substring(0, dataPrefixLength + trimLen) - .TrimEnd(new char[] {ResetCaretChar, NewLineChar, Tabchar}); + .TrimEnd(ResetCaretChar, NewLineChar, Tabchar); sb.Append(serializedInfo + ResetCaretChar + NewLineChar); return sb.ToString(); diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs index 59934f8af..4294d211f 100644 --- a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs +++ b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.Globalization; +using System.Text; using FluentAssertions; using NUnit.Framework; @@ -201,14 +203,56 @@ public void LoopReferencesTest() } [Test] - public void ArraySerializationTest() + public void Array_ShouldBeSerializedAs_JSON() { var array = new int[]{1, 2, 3}; - var expected = array.ToString(); var printer = ObjectPrinter .For(); var ans = printer.PrintToString(array); - ans.Should().Be(expected); + + var expected = new StringBuilder(); + expected.Append("Int32[]\r\n"); + foreach(var i in array) + expected.Append("\t" + i.ToString() + "\r\n"); + + ans.Should().Be(expected.ToString()); + } + + [Test] + public void List_ShouldBeSerializedAs_JSON() + { + var list = new List {1, 2, 3}; + var printer = ObjectPrinter + .For>(); + var ans = printer.PrintToString(list); + + var expected = new StringBuilder(); + expected.Append("List`1\r\n"); + foreach(var i in list) + expected.Append("\t" + i.ToString() + "\r\n"); + + ans.Should().Be(expected.ToString()); + } + + [Test] + public void Dictionary_ShouldBeSerializedAs_JSON() + { + var dict = new Dictionary + { + {1, "one"}, + {2, "two"}, + {3, "three"} + }; + var printer = ObjectPrinter + .For>(); + var ans = printer.PrintToString(dict); + + var expected = new StringBuilder(); + expected.Append("Dictionary`2\r\n"); + foreach(var i in dict) + expected.Append("\t" + i.Key + " = " + i.Value + "\r\n"); + + ans.Should().Be(expected.ToString()); } } } \ No newline at end of file From 948d9e166aa52043a9f464c1c0b8de412a18a7db Mon Sep 17 00:00:00 2001 From: Foma Medvedev Date: Mon, 24 Nov 2025 22:22:57 +0500 Subject: [PATCH 6/7] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=B7=D0=B0=D0=BC=D0=B5=D1=87=D0=B0=D0=BD=D0=B8=D1=8F?= =?UTF-8?q?=20(=D0=BA=D1=80=D0=BE=D0=BC=D0=B5=20=D0=BE=D0=B4=D0=B8=D0=BD?= =?UTF-8?q?=D0=B0=D0=BA=D0=BE=D0=B2=D1=8B=D1=85=20=D0=B8=D0=BC=D1=91=D0=BD?= =?UTF-8?q?=20=D0=B8=20=D1=81=D0=BB=D0=BE=D0=B2=D0=B0=D1=80=D0=B5=D0=B9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ObjectPrinting/PrintingConfig.cs | 154 +++++++++++------- ObjectPrinting/PropertyPrintingConfig.cs | 32 ++-- .../PropertyPrintingConfigExtensions.cs | 4 +- ObjectPrinting/Serializers.cs | 23 --- .../Tests/ObjectPrinterAcceptanceTests.cs | 107 +++++++++--- ObjectPrinting/Tests/Person.cs | 12 +- 6 files changed, 220 insertions(+), 112 deletions(-) delete mode 100644 ObjectPrinting/Serializers.cs diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index 569f071c0..fb99dc0ee 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -11,14 +11,14 @@ namespace ObjectPrinting { public class PrintingConfig { + public delegate string SerializerDelegate(object? obj, int identationLevel, int depthLevel); private readonly IReadOnlySet excludedTypes; - private readonly IReadOnlyDictionary typeSerializers; - private readonly HashSet serializedObjects; + private readonly IReadOnlyDictionary typeSerializers; private readonly IReadOnlyDictionary customCultureInfos; - private readonly IReadOnlyDictionary propertySerializers; + private readonly IReadOnlyDictionary propertySerializers; private readonly IReadOnlyDictionary maxStringLength; private readonly IReadOnlySet excludedProperties; - private const int MaxDeepnessLevel = 2; + private const int MaxDeepnessLevel = 3; private const char Tabchar = '\t'; private const char ResetCaretChar = '\r'; private const char NewLineChar = '\n'; @@ -36,26 +36,23 @@ public class PrintingConfig public PrintingConfig() : this( new HashSet(), - new Dictionary(), - new HashSet(ReferenceEqualityComparer.Instance), + new Dictionary(), new Dictionary(), - new Dictionary(), + new Dictionary(), new Dictionary(), new HashSet()) {} private PrintingConfig( IReadOnlySet excludedTypes, - IReadOnlyDictionary typeSerializers, - HashSet serializedObjects, + IReadOnlyDictionary typeSerializers, IReadOnlyDictionary customCultureInfos, - IReadOnlyDictionary propertySerializers, + IReadOnlyDictionary propertySerializers, IReadOnlyDictionary maxStringLength, IReadOnlySet excludedProperties) { this.excludedTypes = excludedTypes; this.typeSerializers = typeSerializers; - this.serializedObjects = serializedObjects; this.customCultureInfos = customCultureInfos; this.propertySerializers = propertySerializers; this.maxStringLength = maxStringLength; @@ -65,25 +62,24 @@ private PrintingConfig( public PrintingConfig ExcludePropertyOfType() { if (excludedTypes.Contains(typeof(T))) - throw new InvalidOperationException($"The type {typeof(T)} is already excluded."); + return this; var tempSet = new HashSet(excludedTypes) { typeof(T) }; return new PrintingConfig(tempSet, typeSerializers, - serializedObjects, customCultureInfos, propertySerializers, maxStringLength, excludedProperties); } - public PrintingConfig WithTypeSerializationStyle(Serializer serializer) + public PrintingConfig WithTypeSerializationStyle(SerializerDelegate serializer) { - var tempDict = new Dictionary(typeSerializers); - if (!tempDict.TryAdd(typeof(T), serializer)) - throw new InvalidOperationException($"Type {typeof(T).FullName} has already been set serialization type"); + var tempDict = new Dictionary(typeSerializers) + { + [typeof(T)] = serializer + }; return new PrintingConfig(excludedTypes, tempDict, - serializedObjects, customCultureInfos, propertySerializers, maxStringLength, @@ -93,26 +89,26 @@ public PrintingConfig WithTypeSerializationStyle(Serializer serialize public PrintingConfig SetTypeCulture(CultureInfo cultureInfo) where T : IFormattable { - var tempDict = new Dictionary(customCultureInfos); - if (!tempDict.TryAdd(typeof(T), cultureInfo)) - throw new InvalidOperationException($"Type {typeof(T).FullName} has already been set culture info"); + var tempDict = new Dictionary(customCultureInfos) + { + [typeof(T)] = cultureInfo + }; return new PrintingConfig(excludedTypes, typeSerializers, - serializedObjects, tempDict, propertySerializers, maxStringLength, excludedProperties); } - public PrintingConfig WithPropertySerialization(string propertyName, Serializer serializer) + public PrintingConfig WithPropertySerialization(string propertyName, SerializerDelegate serializer) { - var tempDict = new Dictionary(propertySerializers); - if (!tempDict.TryAdd(propertyName, serializer)) - throw new InvalidOperationException($"Property {propertyName} has already been set serialization type"); + var tempDict = new Dictionary(propertySerializers) + { + [propertyName] = serializer + }; return new PrintingConfig(excludedTypes, typeSerializers, - serializedObjects, customCultureInfos, tempDict, maxStringLength, @@ -121,13 +117,14 @@ public PrintingConfig WithPropertySerialization(string propertyName, Ser public PrintingConfig TrimStringToLength(Expression> propertyExpr, int length) { - var member = (MemberExpression)propertyExpr.Body; - var tempDict = new Dictionary(maxStringLength); - if (!tempDict.TryAdd(member.Member.Name, length)) - throw new InvalidOperationException($"Property {member.Member.Name} has already been trim length"); + if (propertyExpr.Body is not MemberExpression member) + throw new ArgumentException("Expression must select a property"); + var tempDict = new Dictionary(maxStringLength) + { + [member.Member.Name] = length + }; return new PrintingConfig(excludedTypes, typeSerializers, - serializedObjects, customCultureInfos, propertySerializers, tempDict, @@ -136,12 +133,11 @@ public PrintingConfig TrimStringToLength(Expression public PrintingConfig Exclude(string propertyName) { - var tempSet = new HashSet(excludedProperties); - if (!tempSet.Add(propertyName)) - throw new InvalidOperationException($"Property {propertyName} has already been excluded"); + if (excludedProperties.Contains(propertyName)) + return this; + var tempSet = new HashSet(excludedProperties) { propertyName }; return new PrintingConfig(excludedTypes, typeSerializers, - serializedObjects, customCultureInfos, propertySerializers, maxStringLength, @@ -166,7 +162,7 @@ private string PrintToString(object? obj, int nestingLevel) return "null" + EnvironmentNewLine; var type = obj.GetType(); - return GetSerializerForType(type).SerializerFunc.Invoke(obj, nestingLevel, 0); + return GetSerializerForType(type).Invoke(obj, nestingLevel, 0); } private string Serialize(object? obj, int nestingLevel, int deepnessLevel) @@ -206,18 +202,56 @@ private string SerializeComplexField(Type type, int deepnessLevel, object obj, i { return SerializeCollection(enumerable, nestingLevel, deepnessLevel); } - var properties = type.GetProperties() - .Where(x => !excludedTypes.Contains(x.PropertyType) + var objectInfo = type.GetMembers(BindingFlags.Instance + | BindingFlags.Public | BindingFlags.NonPublic) + .Where(x => x.MemberType is MemberTypes.Field or MemberTypes.Property) + .Where(x => !IsBackingField(x) && !IsShadowingProperty(x, type)) + .Where(x => !excludedTypes.Contains(ToMemberType(x)) && !excludedProperties.Contains(x.Name)); - foreach (var propertyInfo in properties) + foreach (var propertyInfo in objectInfo) { if (IsDeepnessOverflow(deepnessLevel)) return deepnessExceededString; - sb.Append(SerializeProperty(propertyInfo, obj, nestingLevel, deepnessLevel)); + sb.Append(SerializePropertyOrField(propertyInfo, obj, nestingLevel, deepnessLevel)); } return sb.ToString(); } + private Type ToMemberType(MemberInfo member) + { + if (member is PropertyInfo propertyInfo) + return propertyInfo.PropertyType; + if (member is FieldInfo fieldInfo) + return fieldInfo.FieldType; + throw new ArgumentException($"{member.GetType().Name} is not a property or field"); + } + + private object? GetMemberValue(MemberInfo member, object? obj) + { + if (member is PropertyInfo propertyInfo) + return propertyInfo.GetValue(obj); + if (member is FieldInfo fieldInfo) + return fieldInfo.GetValue(obj); + throw new ArgumentException($"{member.GetType().Name} is not a property or field"); + } + + private bool IsBackingField(MemberInfo member) + { + return member.Name.Contains("k__BackingField"); + } + + private bool IsShadowingProperty(MemberInfo member, Type parent) + { + if (member is not FieldInfo field) + return false; + var memberName = member.Name.ToLower().TrimStart('_'); + var shadowedField = parent.GetProperty(memberName, BindingFlags.Instance | + BindingFlags.Public | + BindingFlags.NonPublic | + BindingFlags.IgnoreCase); + return shadowedField is not null; + } + private string SerializeCollection(IEnumerable enumerable, int nestingLevel, int deepnessLevel) { var sb = new StringBuilder(); @@ -231,7 +265,7 @@ private string SerializeCollection(IEnumerable enumerable, int nestingLevel, int if (isKeyValuePair) sb.Append(SerializeDictionaryElement(obj, deepnessLevel + 1)); else - sb.Append(GetSerializerForType(obj.GetType()).SerializerFunc + sb.Append(GetSerializerForType(obj.GetType()) .Invoke(obj, nestingLevel + 1, deepnessLevel + 1)); sb.Append(new string(Tabchar, nestingLevel + 1)); } @@ -254,49 +288,55 @@ private bool IsDeepnessOverflow(int deepnessLevel) return deepnessLevel >= MaxDeepnessLevel; } - private string SerializeProperty(PropertyInfo propertyInfo, object obj, int nestingLevel, int deepnessLevel) + private string SerializePropertyOrField(MemberInfo memberInfo, object obj, int nestingLevel, int deepnessLevel) { var sb = new StringBuilder(); - if (IsPropertyNeedsUniqueSerialization(propertyInfo.Name, out var serializer)) + if (IsPropertyNeedsUniqueSerialization(memberInfo.Name, out var serializer)) { - sb.Append(BuildSerializedField(propertyInfo, serializer, obj, nestingLevel, deepnessLevel)); + var newData = BuildSerializedField(memberInfo, serializer, obj, nestingLevel, deepnessLevel) + .TrimEnd(ResetCaretChar, NewLineChar, Tabchar); + sb.Append(newData + EnvironmentNewLine); return sb.ToString(); } - if (IsPropertyNeedsTrim(propertyInfo.Name, out var trimLen)) + if (IsPropertyNeedsTrim(memberInfo.Name, out var trimLen)) { var identation = new string(Tabchar, nestingLevel + 1); - var serializedInfo = BuildSerializedField(propertyInfo, null, obj, nestingLevel, deepnessLevel); - var dataPrefixLength = identation.Length + propertyInfo.Name.Length + EqualityWithSpaces.Length; + var serializedInfo = BuildSerializedField(memberInfo, null, obj, nestingLevel, deepnessLevel); + var dataPrefixLength = identation.Length + memberInfo.Name.Length + EqualityWithSpaces.Length; serializedInfo = serializedInfo.Substring(0, dataPrefixLength + trimLen) .TrimEnd(ResetCaretChar, NewLineChar, Tabchar); - sb.Append(serializedInfo + ResetCaretChar + NewLineChar); + sb.Append(serializedInfo + EnvironmentNewLine); return sb.ToString(); } - sb.Append(BuildSerializedField(propertyInfo, null, obj, nestingLevel, deepnessLevel)); + sb.Append(BuildSerializedField(memberInfo, null, obj, nestingLevel, deepnessLevel)); return sb.ToString(); } - private string BuildSerializedField(PropertyInfo propertyInfo, Serializer? serializer, + private string BuildSerializedField(MemberInfo memberInfo, SerializerDelegate? serializer, object? obj, int nestingLevel, int deepnessLevel) { + var memberValue = GetMemberValue(memberInfo, obj); + if (memberValue is null) + return string.Empty; var identation = new string(Tabchar, nestingLevel + 1); var sb = new StringBuilder(); - sb.Append(identation + propertyInfo.Name + EqualityWithSpaces); + sb.Append(identation + memberInfo.Name + EqualityWithSpaces); if (serializer is null) - serializer = GetSerializerForType(propertyInfo.PropertyType); - sb.Append(serializer.SerializerFunc.Invoke(propertyInfo.GetValue(obj), nestingLevel + 1, deepnessLevel + 1)); + serializer = GetSerializerForType(memberValue.GetType()); + sb.Append(serializer.Invoke(GetMemberValue(memberInfo, obj), nestingLevel + 1, deepnessLevel + 1)); return sb.ToString(); } - private Serializer GetSerializerForType(Type type) + private SerializerDelegate GetSerializerForType(Type type) { if (typeSerializers.TryGetValue(type, out var forType)) return forType; - return new Serializer(Serialize); + return Serialize; } - private bool IsPropertyNeedsUniqueSerialization(string propertyName, out Serializer? serializer) + private bool IsPropertyNeedsUniqueSerialization(string propertyName, + out SerializerDelegate? serializer) { serializer = null; if (propertySerializers.TryGetValue(propertyName, out var propertySerializer)) diff --git a/ObjectPrinting/PropertyPrintingConfig.cs b/ObjectPrinting/PropertyPrintingConfig.cs index fbe60aea5..c18e83ac7 100644 --- a/ObjectPrinting/PropertyPrintingConfig.cs +++ b/ObjectPrinting/PropertyPrintingConfig.cs @@ -6,25 +6,35 @@ namespace ObjectPrinting; public class PropertyPrintingConfig { private readonly PrintingConfig config; - private readonly Expression>? propertySelector; + private readonly Expression>? selector; - public PropertyPrintingConfig(PrintingConfig config, Expression>? propertySelector) + public PropertyPrintingConfig(PrintingConfig config, Expression>? selector) { this.config = config; - this.propertySelector = ValidateExpression(propertySelector); + this.selector = ValidateExpression(selector); } - private Expression> ValidateExpression(Expression>? propertySelector) + private Expression> ValidateExpression(Expression>? validatingSelector) { - if (propertySelector == null) - throw new ArgumentNullException(nameof(propertySelector)); - if (propertySelector.Body is not MemberExpression memberExpression) - throw new ArgumentException($"{nameof(propertySelector)} must select property"); - return propertySelector; + if (validatingSelector == null) + throw new ArgumentNullException(nameof(validatingSelector)); + if (validatingSelector.Body is not MemberExpression memberExpression) + throw new ArgumentException($"{nameof(validatingSelector)} must select property or field"); + if (!IsDirectPropertyAccess(memberExpression, validatingSelector.Parameters[0])) + throw new ArgumentException("Property selector should only access properties of the parameter, not external variables"); + return validatingSelector; } + private bool IsDirectPropertyAccess(Expression? expression, ParameterExpression parameter) + { + while (expression is MemberExpression member) + expression = member.Expression; + return expression == parameter; + } + + public PrintingConfig ParentConfig => config; - public Expression>? PropertySelector => propertySelector; + public Expression>? PropertySelector => selector; - public string PropertyName => ((MemberExpression)propertySelector!.Body).Member.Name; + public string PropertyName => ((MemberExpression)selector!.Body).Member.Name; } \ No newline at end of file diff --git a/ObjectPrinting/PropertyPrintingConfigExtensions.cs b/ObjectPrinting/PropertyPrintingConfigExtensions.cs index 284a98902..9aeefb959 100644 --- a/ObjectPrinting/PropertyPrintingConfigExtensions.cs +++ b/ObjectPrinting/PropertyPrintingConfigExtensions.cs @@ -1,9 +1,11 @@ +using System; + namespace ObjectPrinting; public static class PropertyPrintingConfigExtensions { public static PrintingConfig SetSerializationStyle - (this PropertyPrintingConfig propertyConfig, Serializer serializer) + (this PropertyPrintingConfig propertyConfig, PrintingConfig.SerializerDelegate serializer) { var propertyName = propertyConfig.PropertyName; return propertyConfig.ParentConfig.WithPropertySerialization(propertyName, serializer); diff --git a/ObjectPrinting/Serializers.cs b/ObjectPrinting/Serializers.cs deleted file mode 100644 index 9ca82a4c3..000000000 --- a/ObjectPrinting/Serializers.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Globalization; -using System.Linq; -using System.Text; - -namespace ObjectPrinting; - -public interface ISerializer -{ - public Func SerializerFunc { get; } - public Func GetSerializerFunc => SerializerFunc; -} - -public class Serializer : ISerializer -{ - public Func SerializerFunc { get; } - public Func GetSerializeFunc => SerializerFunc; - - public Serializer(Func serializerFunc) - { - this.SerializerFunc = serializerFunc; - } -} \ No newline at end of file diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs index 4294d211f..f772732ff 100644 --- a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs +++ b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Drawing; using System.Globalization; using System.Text; using FluentAssertions; @@ -15,17 +16,19 @@ public void DemonstrationTest() { var person = new Person { Name = "Alex", Surname = "Smith", Age = 19, Height = 180.5}; person.Phone = new Phone {Name = "Смартфон Vivo", Owner = person}; + PrintingConfig.SerializerDelegate phoneSerializer + = (obj, nestingLevel, deepnessLevel) => "MyPhone" + Environment.NewLine; var printer = ObjectPrinter.For() //1. Исключить из сериализации свойства определенного типа .ExcludePropertyOfType() //2. Указать альтернативный способ сериализации для определенного типа - .WithTypeSerializationStyle(new Serializer((obj, nestingLevel, deepnessLevel) => "MyPhone" + Environment.NewLine)) + .WithTypeSerializationStyle(phoneSerializer) //3. Для числовых типов указать культуру .SetTypeCulture(CultureInfo.InvariantCulture) //4. Настроить сериализацию конкретного свойства - .GetPropertyByName(x => x.Name) - .SetSerializationStyle(new Serializer((obj, nestingLevel, deepnessLevel) => "MyName" + Environment.NewLine)) + .GetPropertyByName(x => x.Name) + .SetSerializationStyle((obj, nestingLevel, deepnessLevel) => "MyName") //5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств) .GetPropertyByName(x => x.Surname).TrimStringToLength(3) //6. Исключить из сериализации конкретное свойства @@ -69,8 +72,8 @@ public void WithTypeSerializationStyle_ShouldSetUniqueSerializationStyle_ToPrope person.Phone = new Phone {Name = "Смартфон Vivo"}; var printer = ObjectPrinter .For() - .WithTypeSerializationStyle(new Serializer((obj, nestingLevel, deepnessLevel) => "999999999" + Environment.NewLine)) - .WithTypeSerializationStyle(new Serializer((obj, nestingLevel, deepnessLevel) => "My phone" + Environment.NewLine)); + .WithTypeSerializationStyle((obj, nestingLevel, deepnessLevel) => "999999999" + Environment.NewLine) + .WithTypeSerializationStyle((obj, nestingLevel, deepnessLevel) => "My phone" + Environment.NewLine); var ans = printer.PrintToString(person); var expected = @@ -79,8 +82,8 @@ public void WithTypeSerializationStyle_ShouldSetUniqueSerializationStyle_ToPrope "Name = Alex\r\n\t" + "Surname = Smith\r\n\t" + "Height = 180,5\r\n\t" + - "Age = 19\r\n\t" + - "Phone = My phone\r\n"; + "Phone = My phone\r\n\t" + + "Age = 19\r\n"; ans.Should().Be(expected); } @@ -101,8 +104,7 @@ public void SetTypeCulture_ShouldSetTypeCulture_ToPropertiesWithThisType() "Name = Alex\r\n\t" + "Surname = Smith\r\n\t" + "Height = 180,5\r\n\t" + - "Age = 19\r\n\t" + - "Phone = null\r\n"; + "Age = 19\r\n"; ans.Should().Be(expected); } @@ -127,7 +129,8 @@ public void SetSerializationStyle_ShouldSetUniqueSerializationStyle_ToGotPropert var person = new Person {Name = "Alex", Surname = "Smith", Age = 19, Height = 180.5}; var printer = ObjectPrinter .For() - .GetPropertyByName(x => x.Name).SetSerializationStyle(new Serializer((obj, nestingLevel, deepnessLevel) => "My name" + Environment.NewLine)); + .GetPropertyByName(x => x.Name) + .SetSerializationStyle((obj, nestingLevel, deepnessLevel) => "My name" + Environment.NewLine); var ans = printer.PrintToString(person); var baseGuid = Guid.Empty.ToString(); @@ -137,8 +140,7 @@ public void SetSerializationStyle_ShouldSetUniqueSerializationStyle_ToGotPropert "Name = My name\r\n\t" + "Surname = Smith\r\n\t" + "Height = 180,5\r\n\t" + - "Age = 19\r\n\t" + - "Phone = null\r\n"; + "Age = 19\r\n"; ans.Should().Be(expected); } @@ -159,8 +161,7 @@ public void TrimStringToLength_ShouldTrimPropertyLength_ToGotProperty() "Name = Alex\r\n\t" + "Surname = Very \r\n\t" + "Height = 180,5\r\n\t" + - "Age = 19\r\n\t" + - "Phone = null\r\n"; + "Age = 19\r\n"; ans.Should().Be(expected); } @@ -186,13 +187,53 @@ public void Exclude_ShouldRemove_GotProperty() ans.Should().Be(expected); } + [Test] + public void PrivateFieldsAndProperties_ShouldBeSerialized() + { + var person = new Person {Name = "Alex", Surname = "Smith", Age = 19, Height = 180.5}; + var secretCode = "1"; + person.SetSecretCode(secretCode); + person.PassportName = "passport"; + var printer = ObjectPrinter + .For() + .GetPropertyByName(x => x.Phone).Exclude(); + var ans = printer.PrintToString(person); + + var baseGuid = Guid.Empty.ToString(); + var expected = + "Person\r\n\t" + + $"Id = {baseGuid}\r\n\t" + + "Name = Alex\r\n\t" + + "Surname = Smith\r\n\t" + + "Height = 180,5\r\n\t" + + "PassportName = passport\r\n\t" + + "Age = 19\r\n\t" + + $"SecretCode = {secretCode}\r\n"; + + ans.Should().Be(expected); + } + [Test] public void LoopReferencesTest() { var baseGuid = Guid.Empty.ToString(); - var expected = $"Person\r\n\tId = {baseGuid}\r\n\tName = Alex\r\n\tSurname = Smith\r\n\tHeight = 180,5" + - $"\r\n\tAge = 19\r\n\tPhone = Phone\r\n\t\tId = {baseGuid}\r\n\t\t" + - "Name = My phone\r\n\t\tOwner = Deepness exceeded!\r\n"; + var expected = + "Person\r\n\t" + + $"Id = {baseGuid}\r\n\t" + + "Name = Alex\r\n\t" + + "Surname = Smith\r\n\t" + + "Height = 180,5\r\n\t" + + "Phone = Phone\r\n\t\t" + + $"Id = {baseGuid}\r\n\t\t" + + "Name = My phone\r\n\t\t" + + "Owner = Person\r\n\t\t\t" + + $"Id = {baseGuid}\r\n\t\t\t" + + "Name = Alex\r\n\t\t\t" + + "Surname = Smith\r\n\t\t\t" + + "Height = 180,5\r\n\t\t\t" + + "Phone = Deepness exceeded!\r\n\t\t\t" + + "Age = 19\r\n\t" + + "Age = 19\r\n"; var person = new Person {Name = "Alex", Surname = "Smith", Age = 19, Height = 180.5}; var phone = new Phone {Name = "My phone", Owner = person}; person.Phone = phone; @@ -207,13 +248,15 @@ public void Array_ShouldBeSerializedAs_JSON() { var array = new int[]{1, 2, 3}; var printer = ObjectPrinter - .For(); + .For() + .WithTypeSerializationStyle((obj, nestingLevel, deepnessLevel) => + obj.GetType() + Environment.NewLine); var ans = printer.PrintToString(array); var expected = new StringBuilder(); expected.Append("Int32[]\r\n"); foreach(var i in array) - expected.Append("\t" + i.ToString() + "\r\n"); + expected.Append("\t" + i.GetType() + "\r\n"); ans.Should().Be(expected.ToString()); } @@ -254,5 +297,31 @@ public void Dictionary_ShouldBeSerializedAs_JSON() ans.Should().Be(expected.ToString()); } + + [Test] + public void PropertyPrintingConfig_ShouldValidate_NullSelector() + { + var config = new PrintingConfig(); + Action test = () => new PropertyPrintingConfig(config, null); + test.Should().Throw(); + } + + [Test] + public void PropertyPrintingConfig_ShouldValidate_NotFieldOrPropertySelector() + { + var config = new PrintingConfig(); + Action test = () => new PropertyPrintingConfig(config, a => "aaaa"); + test.Should().Throw(); + } + + [Test] + public void PropertyPrintingConfig_ShouldValidate_ExternalVariables() + { + var sd = new Rectangle(); + var config = new PrintingConfig(); + + Action test = () => new PropertyPrintingConfig(config, x => sd.Bottom); + test.Should().Throw(); + } } } \ No newline at end of file diff --git a/ObjectPrinting/Tests/Person.cs b/ObjectPrinting/Tests/Person.cs index d49cb581f..bb4fc14c0 100644 --- a/ObjectPrinting/Tests/Person.cs +++ b/ObjectPrinting/Tests/Person.cs @@ -8,8 +8,18 @@ public class Person public string Name { get; set; } public string Surname { get; set; } public double Height { get; set; } - public int Age { get; set; } public Phone Phone { get; set; } + public int Age; + private string SecretCode; + private string _passportName; + + public string PassportName + { + get => _passportName; + set => _passportName = value; + } + + public void SetSecretCode(string code) => this.SecretCode = code; } public class Phone From 483f2762ba30ff40a603299b1791db430fde17df Mon Sep 17 00:00:00 2001 From: Foma Medvedev Date: Tue, 25 Nov 2025 14:56:48 +0500 Subject: [PATCH 7/7] =?UTF-8?q?=D0=97=D0=B0=D0=BA=D1=80=D1=8B=D0=BB=20?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D0=B0=D0=B2=D1=88=D0=B8=D0=B5=D1=81=D1=8F=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=BC=D0=B5=D1=87=D0=B0=D0=BD=D0=B8=D1=8F=20(?= =?UTF-8?q?=D1=81=D0=BB=D0=BE=D0=B2=D0=B0=D1=80=D0=B8,=20=D1=81=D0=B5?= =?UTF-8?q?=D1=80=D0=B8=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20?= =?UTF-8?q?=D1=81=D0=B2=D0=BE=D0=B9=D1=81=D1=82=D0=B2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ObjectPrinting/PrintingConfig.cs | 95 +++++++++++-------- ObjectPrinting/PropertyPath.cs | 29 ++++++ ObjectPrinting/PropertyPrintingConfig.cs | 22 +++-- .../PropertyPrintingConfigExtensions.cs | 13 ++- .../Tests/ObjectPrinterAcceptanceTests.cs | 84 ++++++++++------ 5 files changed, 168 insertions(+), 75 deletions(-) create mode 100644 ObjectPrinting/PropertyPath.cs diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index fb99dc0ee..e9e09452d 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -11,7 +11,7 @@ namespace ObjectPrinting { public class PrintingConfig { - public delegate string SerializerDelegate(object? obj, int identationLevel, int depthLevel); + public delegate string SerializerDelegate(object? obj, int identationLevel, int depthLevel, List pathSegments); private readonly IReadOnlySet excludedTypes; private readonly IReadOnlyDictionary typeSerializers; private readonly IReadOnlyDictionary customCultureInfos; @@ -101,11 +101,11 @@ public PrintingConfig SetTypeCulture(CultureInfo cultureInfo) excludedProperties); } - public PrintingConfig WithPropertySerialization(string propertyName, SerializerDelegate serializer) + public PrintingConfig WithPropertySerialization(PropertyPath propertyName, SerializerDelegate serializer) { var tempDict = new Dictionary(propertySerializers) { - [propertyName] = serializer + [propertyName.ToString()] = serializer }; return new PrintingConfig(excludedTypes, typeSerializers, @@ -121,7 +121,7 @@ public PrintingConfig TrimStringToLength(Expression throw new ArgumentException("Expression must select a property"); var tempDict = new Dictionary(maxStringLength) { - [member.Member.Name] = length + [PropertyPath.ConvertFromSelector(propertyExpr).ToString()] = length }; return new PrintingConfig(excludedTypes, typeSerializers, @@ -131,11 +131,11 @@ public PrintingConfig TrimStringToLength(Expression excludedProperties); } - public PrintingConfig Exclude(string propertyName) + public PrintingConfig Exclude(PropertyPath propertyName) { - if (excludedProperties.Contains(propertyName)) + if (excludedProperties.Contains(propertyName.ToString())) return this; - var tempSet = new HashSet(excludedProperties) { propertyName }; + var tempSet = new HashSet(excludedProperties) { propertyName.ToString() }; return new PrintingConfig(excludedTypes, typeSerializers, customCultureInfos, @@ -162,23 +162,24 @@ private string PrintToString(object? obj, int nestingLevel) return "null" + EnvironmentNewLine; var type = obj.GetType(); - return GetSerializerForType(type).Invoke(obj, nestingLevel, 0); + var path = new List{type.Name}; + return GetSerializerForType(type).Invoke(obj, nestingLevel, 0, path); } - private string Serialize(object? obj, int nestingLevel, int deepnessLevel) + private string Serialize(object? obj, int nestingLevel, int deepnessLevel, List pathSegments) { if (obj is null) return "null" + EnvironmentNewLine; var type = obj.GetType(); - if (TrySerializeAsBaseFields(obj, type, nestingLevel, out var serialized) && serialized is not null) + if (TrySerializeAsBaseFields(obj, type, out var serialized) && serialized is not null) return serialized; var sb = new StringBuilder(); - sb.Append(SerializeComplexField(type, deepnessLevel, obj, nestingLevel)); + sb.Append(SerializeComplexField(type, deepnessLevel, obj, nestingLevel, pathSegments)); return sb.ToString(); } - private bool TrySerializeAsBaseFields(object obj, Type type, int nestingLevel, out string? result) + private bool TrySerializeAsBaseFields(object obj, Type type, out string? result) { result = null; if (customCultureInfos.TryGetValue(type, out var info)) @@ -188,31 +189,38 @@ private bool TrySerializeAsBaseFields(object obj, Type type, int nestingLevel, o } if (_finalTypes.Contains(type)) { - result = obj.ToString() + EnvironmentNewLine; + result = obj + EnvironmentNewLine; return true; } return false; } - private string SerializeComplexField(Type type, int deepnessLevel, object obj, int nestingLevel) + private string SerializeComplexField(Type type, int deepnessLevel, object obj, int nestingLevel, List pathSegments) { var sb = new StringBuilder(); sb.AppendLine(type.Name); if (obj is IEnumerable enumerable && type != typeof(string)) - { - return SerializeCollection(enumerable, nestingLevel, deepnessLevel); - } + return SerializeCollection(enumerable, nestingLevel, deepnessLevel, pathSegments); + + var fullFieldName = new PropertyPath(pathSegments); var objectInfo = type.GetMembers(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) .Where(x => x.MemberType is MemberTypes.Field or MemberTypes.Property) .Where(x => !IsBackingField(x) && !IsShadowingProperty(x, type)) - .Where(x => !excludedTypes.Contains(ToMemberType(x)) - && !excludedProperties.Contains(x.Name)); + .Where(x => !excludedTypes.Contains(ToMemberType(x))); foreach (var propertyInfo in objectInfo) { + pathSegments.Add(propertyInfo.Name); + fullFieldName = new PropertyPath(pathSegments); + if (excludedProperties.Contains(fullFieldName.ToString())) + { + pathSegments.RemoveAt(pathSegments.Count - 1); + continue; + } if (IsDeepnessOverflow(deepnessLevel)) return deepnessExceededString; - sb.Append(SerializePropertyOrField(propertyInfo, obj, nestingLevel, deepnessLevel)); + sb.Append(SerializePropertyOrField(propertyInfo, obj, nestingLevel, deepnessLevel, pathSegments)); + pathSegments.RemoveAt(pathSegments.Count - 1); } return sb.ToString(); } @@ -252,7 +260,8 @@ private bool IsShadowingProperty(MemberInfo member, Type parent) return shadowedField is not null; } - private string SerializeCollection(IEnumerable enumerable, int nestingLevel, int deepnessLevel) + private string SerializeCollection(IEnumerable enumerable, int nestingLevel, int deepnessLevel, + List pathSegments) { var sb = new StringBuilder(); sb.AppendLine(enumerable.GetType().Name); @@ -260,27 +269,36 @@ private string SerializeCollection(IEnumerable enumerable, int nestingLevel, int foreach (var obj in enumerable) { var objType = obj.GetType(); + pathSegments.Add(objType.Name); var isKeyValuePair = objType.IsGenericType && objType.GetGenericTypeDefinition() == typeof(KeyValuePair<,>); if (isKeyValuePair) - sb.Append(SerializeDictionaryElement(obj, deepnessLevel + 1)); + sb.Append(SerializeDictionaryElement(obj, deepnessLevel + 1, pathSegments)); else sb.Append(GetSerializerForType(obj.GetType()) - .Invoke(obj, nestingLevel + 1, deepnessLevel + 1)); + .Invoke(obj, nestingLevel + 1, deepnessLevel + 1, pathSegments)); + pathSegments.RemoveAt(pathSegments.Count - 1); sb.Append(new string(Tabchar, nestingLevel + 1)); } sb.Remove(sb.Length - 1, 1); return sb.ToString(); } - private string SerializeDictionaryElement(object obj, int deepnessLevel) + private string SerializeDictionaryElement(object obj, int deepnessLevel, List pathSegments) { if (IsDeepnessOverflow(deepnessLevel)) return deepnessExceededString; var type = obj.GetType(); var key = type.GetProperty("Key")!.GetValue(obj); var value = type.GetProperty("Value")!.GetValue(obj); - return new string(key + " = " + value + EnvironmentNewLine); + var sb = new StringBuilder(); + sb.Append(GetSerializerForType(key!.GetType()).Invoke(key, 0, + deepnessLevel + 1, pathSegments).TrimEnd(ResetCaretChar, NewLineChar, Tabchar)); + sb.Append(EqualityWithSpaces); + sb.Append(GetSerializerForType(value!.GetType()).Invoke(value, 0, + deepnessLevel + 1, pathSegments).TrimEnd(ResetCaretChar, NewLineChar, Tabchar)); + sb.Append(EnvironmentNewLine); + return sb.ToString(); } private bool IsDeepnessOverflow(int deepnessLevel) @@ -288,20 +306,22 @@ private bool IsDeepnessOverflow(int deepnessLevel) return deepnessLevel >= MaxDeepnessLevel; } - private string SerializePropertyOrField(MemberInfo memberInfo, object obj, int nestingLevel, int deepnessLevel) + private string SerializePropertyOrField(MemberInfo memberInfo, object obj, int nestingLevel, + int deepnessLevel, List path) { var sb = new StringBuilder(); - if (IsPropertyNeedsUniqueSerialization(memberInfo.Name, out var serializer)) + var fullPropertyName = new PropertyPath(path); + if (IsPropertyNeedsUniqueSerialization(fullPropertyName, out var serializer)) { - var newData = BuildSerializedField(memberInfo, serializer, obj, nestingLevel, deepnessLevel) + var newData = BuildSerializedField(memberInfo, serializer, obj, nestingLevel, deepnessLevel, path) .TrimEnd(ResetCaretChar, NewLineChar, Tabchar); sb.Append(newData + EnvironmentNewLine); return sb.ToString(); } - if (IsPropertyNeedsTrim(memberInfo.Name, out var trimLen)) + if (IsPropertyNeedsTrim(fullPropertyName, out var trimLen)) { var identation = new string(Tabchar, nestingLevel + 1); - var serializedInfo = BuildSerializedField(memberInfo, null, obj, nestingLevel, deepnessLevel); + var serializedInfo = BuildSerializedField(memberInfo, null, obj, nestingLevel, deepnessLevel, path); var dataPrefixLength = identation.Length + memberInfo.Name.Length + EqualityWithSpaces.Length; serializedInfo = serializedInfo.Substring(0, dataPrefixLength + trimLen) .TrimEnd(ResetCaretChar, NewLineChar, Tabchar); @@ -309,12 +329,12 @@ private string SerializePropertyOrField(MemberInfo memberInfo, object obj, int n sb.Append(serializedInfo + EnvironmentNewLine); return sb.ToString(); } - sb.Append(BuildSerializedField(memberInfo, null, obj, nestingLevel, deepnessLevel)); + sb.Append(BuildSerializedField(memberInfo, null, obj, nestingLevel, deepnessLevel, path)); return sb.ToString(); } private string BuildSerializedField(MemberInfo memberInfo, SerializerDelegate? serializer, - object? obj, int nestingLevel, int deepnessLevel) + object? obj, int nestingLevel, int deepnessLevel, List path) { var memberValue = GetMemberValue(memberInfo, obj); if (memberValue is null) @@ -324,7 +344,8 @@ private string BuildSerializedField(MemberInfo memberInfo, SerializerDelegate? s sb.Append(identation + memberInfo.Name + EqualityWithSpaces); if (serializer is null) serializer = GetSerializerForType(memberValue.GetType()); - sb.Append(serializer.Invoke(GetMemberValue(memberInfo, obj), nestingLevel + 1, deepnessLevel + 1)); + sb.Append(serializer.Invoke(GetMemberValue(memberInfo, obj), nestingLevel + 1, + deepnessLevel + 1, path)); return sb.ToString(); } @@ -335,11 +356,11 @@ private SerializerDelegate GetSerializerForType(Type type) return Serialize; } - private bool IsPropertyNeedsUniqueSerialization(string propertyName, + private bool IsPropertyNeedsUniqueSerialization(PropertyPath propertyName, out SerializerDelegate? serializer) { serializer = null; - if (propertySerializers.TryGetValue(propertyName, out var propertySerializer)) + if (propertySerializers.TryGetValue(propertyName.ToString(), out var propertySerializer)) { serializer = propertySerializer; return true; @@ -347,10 +368,10 @@ private bool IsPropertyNeedsUniqueSerialization(string propertyName, return false; } - private bool IsPropertyNeedsTrim(string propertyName, out int trimLength) + private bool IsPropertyNeedsTrim(PropertyPath fullPropertyName, out int trimLength) { trimLength = 0; - if (maxStringLength.TryGetValue(propertyName, out var value)) + if (maxStringLength.TryGetValue(fullPropertyName.ToString(), out var value)) { trimLength = value; return true; diff --git a/ObjectPrinting/PropertyPath.cs b/ObjectPrinting/PropertyPath.cs new file mode 100644 index 000000000..6bcc73005 --- /dev/null +++ b/ObjectPrinting/PropertyPath.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq.Expressions; + +namespace ObjectPrinting; + +public record PropertyPath +{ + private List pathSegments; + public PropertyPath(List pathSegments) => this.pathSegments = pathSegments; + + public override string ToString() => string.Join(".", pathSegments); + + public static PropertyPath ConvertFromSelector(Expression> selector) + { + var segments = new List(); + var expression = selector.Body; + + while (expression is MemberExpression member) + { + segments.Add(member.Member.Name); + expression = member.Expression; + } + segments.Add(typeof(TOwner).Name); + + segments.Reverse(); + return new PropertyPath(segments); + } +} \ No newline at end of file diff --git a/ObjectPrinting/PropertyPrintingConfig.cs b/ObjectPrinting/PropertyPrintingConfig.cs index c18e83ac7..db43cbc48 100644 --- a/ObjectPrinting/PropertyPrintingConfig.cs +++ b/ObjectPrinting/PropertyPrintingConfig.cs @@ -3,15 +3,24 @@ namespace ObjectPrinting; -public class PropertyPrintingConfig +internal interface IPropertyPrintingConfigAccessor +{ + PrintingConfig ParentConfig { get; } + Expression>? PropertySelector { get; } + PropertyPath PropertyPath { get; } +} + +public class PropertyPrintingConfig : IPropertyPrintingConfigAccessor { private readonly PrintingConfig config; private readonly Expression>? selector; + private readonly PropertyPath propertyPath; public PropertyPrintingConfig(PrintingConfig config, Expression>? selector) { this.config = config; this.selector = ValidateExpression(selector); + this.propertyPath = PropertyPath.ConvertFromSelector(selector!); // Не null т.к. валидируем это } private Expression> ValidateExpression(Expression>? validatingSelector) @@ -31,10 +40,11 @@ private bool IsDirectPropertyAccess(Expression? expression, ParameterExpression expression = member.Expression; return expression == parameter; } - - public PrintingConfig ParentConfig => config; - public Expression>? PropertySelector => selector; - - public string PropertyName => ((MemberExpression)selector!.Body).Member.Name; + PrintingConfig IPropertyPrintingConfigAccessor.ParentConfig => config; + + Expression>? IPropertyPrintingConfigAccessor.PropertySelector => selector; + + PropertyPath IPropertyPrintingConfigAccessor.PropertyPath => + propertyPath; } \ No newline at end of file diff --git a/ObjectPrinting/PropertyPrintingConfigExtensions.cs b/ObjectPrinting/PropertyPrintingConfigExtensions.cs index 9aeefb959..bccec6e79 100644 --- a/ObjectPrinting/PropertyPrintingConfigExtensions.cs +++ b/ObjectPrinting/PropertyPrintingConfigExtensions.cs @@ -7,20 +7,23 @@ public static class PropertyPrintingConfigExtensions public static PrintingConfig SetSerializationStyle (this PropertyPrintingConfig propertyConfig, PrintingConfig.SerializerDelegate serializer) { - var propertyName = propertyConfig.PropertyName; - return propertyConfig.ParentConfig.WithPropertySerialization(propertyName, serializer); + var accessor = (IPropertyPrintingConfigAccessor)propertyConfig; + var propertyName = PropertyPath.ConvertFromSelector(accessor.PropertySelector!); + return accessor.ParentConfig.WithPropertySerialization(propertyName, serializer); } public static PrintingConfig TrimStringToLength (this PropertyPrintingConfig propertyConfig, int length) { - return propertyConfig.ParentConfig.TrimStringToLength(propertyConfig.PropertySelector!, length); + var accessor = (IPropertyPrintingConfigAccessor)propertyConfig; + return accessor.ParentConfig.TrimStringToLength(accessor.PropertySelector!, length); } public static PrintingConfig Exclude (this PropertyPrintingConfig propertyConfig) { - var propertyName = propertyConfig.PropertyName; - return propertyConfig.ParentConfig.Exclude(propertyName); + var accessor = (IPropertyPrintingConfigAccessor)propertyConfig; + var propertyName = PropertyPath.ConvertFromSelector(accessor.PropertySelector!); + return accessor.ParentConfig.Exclude(propertyName); } } \ No newline at end of file diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs index f772732ff..31ba26f40 100644 --- a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs +++ b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs @@ -17,7 +17,7 @@ public void DemonstrationTest() var person = new Person { Name = "Alex", Surname = "Smith", Age = 19, Height = 180.5}; person.Phone = new Phone {Name = "Смартфон Vivo", Owner = person}; PrintingConfig.SerializerDelegate phoneSerializer - = (obj, nestingLevel, deepnessLevel) => "MyPhone" + Environment.NewLine; + = (obj, nestingLevel, deepnessLevel, path) => "MyPhone" + Environment.NewLine; var printer = ObjectPrinter.For() //1. Исключить из сериализации свойства определенного типа @@ -28,7 +28,7 @@ PrintingConfig.SerializerDelegate phoneSerializer .SetTypeCulture(CultureInfo.InvariantCulture) //4. Настроить сериализацию конкретного свойства .GetPropertyByName(x => x.Name) - .SetSerializationStyle((obj, nestingLevel, deepnessLevel) => "MyName") + .SetSerializationStyle((obj, nestingLevel, deepnessLevel, path) => "MyName") //5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств) .GetPropertyByName(x => x.Surname).TrimStringToLength(3) //6. Исключить из сериализации конкретное свойства @@ -72,8 +72,8 @@ public void WithTypeSerializationStyle_ShouldSetUniqueSerializationStyle_ToPrope person.Phone = new Phone {Name = "Смартфон Vivo"}; var printer = ObjectPrinter .For() - .WithTypeSerializationStyle((obj, nestingLevel, deepnessLevel) => "999999999" + Environment.NewLine) - .WithTypeSerializationStyle((obj, nestingLevel, deepnessLevel) => "My phone" + Environment.NewLine); + .WithTypeSerializationStyle((obj, nestingLevel, deepnessLevel, path) => "999999999" + Environment.NewLine) + .WithTypeSerializationStyle((obj, nestingLevel, deepnessLevel, path) => "My phone" + Environment.NewLine); var ans = printer.PrintToString(person); var expected = @@ -109,19 +109,19 @@ public void SetTypeCulture_ShouldSetTypeCulture_ToPropertiesWithThisType() ans.Should().Be(expected); } - [Test] - public void GetPropertyByName_ShouldReturn_PropertyInfoByName() - { - var person = new Person {Name = "Alex", Surname = "Smith", Age = 19, Height = 180.5}; - var printer = ObjectPrinter - .For() - .GetPropertyByName(x => x.Id); - var ans = printer.PropertyName; - - var expected = "Id"; - - ans.Should().Be(expected); - } + // [Test] + // public void GetPropertyByName_ShouldReturn_PropertyInfoByName() + // { + // var person = new Person {Name = "Alex", Surname = "Smith", Age = 19, Height = 180.5}; + // var printer = ObjectPrinter + // .For() + // .GetPropertyByName(x => x.Id); + // var ans = printer.PropertyName; + // + // var expected = "Id"; + // + // ans.Should().Be(expected); + // } [Test] public void SetSerializationStyle_ShouldSetUniqueSerializationStyle_ToGotProperty() @@ -130,7 +130,7 @@ public void SetSerializationStyle_ShouldSetUniqueSerializationStyle_ToGotPropert var printer = ObjectPrinter .For() .GetPropertyByName(x => x.Name) - .SetSerializationStyle((obj, nestingLevel, deepnessLevel) => "My name" + Environment.NewLine); + .SetSerializationStyle((obj, nestingLevel, deepnessLevel, path) => "My name" + Environment.NewLine); var ans = printer.PrintToString(person); var baseGuid = Guid.Empty.ToString(); @@ -213,10 +213,44 @@ public void PrivateFieldsAndProperties_ShouldBeSerialized() ans.Should().Be(expected); } + [Test] + public void SetSerializeStyle_ForFieldOrProperty_ShouldAffect_OnlyOnThisFieldOrProperty() + { + var person = new Person {Name = "Alex", Surname = "Smith", Height = 180.5, Age = 19}; + var phone = new Phone {Name = "Phone"}; + person.Phone = phone; + var printer = ObjectPrinter + .For() + .GetPropertyByName(x => x.Name) + .SetSerializationStyle((obj, nestingLevel, deepnessLevel, path) => "My name"); + var ans = printer.PrintToString(person); + + var baseGuid = Guid.Empty.ToString(); + var expected = + "Person\r\n\t" + + $"Id = {baseGuid}\r\n\t" + + "Name = My name\r\n\t" + + "Surname = Smith\r\n\t" + + "Height = 180,5\r\n\t" + + "Phone = Phone\r\n\t\t" + + $"Id = {baseGuid}\r\n\t\t" + + "Name = Phone\r\n\t" + + "Age = 19\r\n"; + + ans.Should().Be(expected); + } + [Test] public void LoopReferencesTest() { var baseGuid = Guid.Empty.ToString(); + var person = new Person {Name = "Alex", Surname = "Smith", Age = 19, Height = 180.5}; + var phone = new Phone {Name = "My phone", Owner = person}; + person.Phone = phone; + var printer = ObjectPrinter + .For(); + var ans = printer.PrintToString(person); + var expected = "Person\r\n\t" + $"Id = {baseGuid}\r\n\t" + @@ -234,12 +268,7 @@ public void LoopReferencesTest() "Phone = Deepness exceeded!\r\n\t\t\t" + "Age = 19\r\n\t" + "Age = 19\r\n"; - var person = new Person {Name = "Alex", Surname = "Smith", Age = 19, Height = 180.5}; - var phone = new Phone {Name = "My phone", Owner = person}; - person.Phone = phone; - var printer = ObjectPrinter - .For(); - var ans = printer.PrintToString(person); + ans.Should().Be(expected); } @@ -249,7 +278,7 @@ public void Array_ShouldBeSerializedAs_JSON() var array = new int[]{1, 2, 3}; var printer = ObjectPrinter .For() - .WithTypeSerializationStyle((obj, nestingLevel, deepnessLevel) => + .WithTypeSerializationStyle((obj, nestingLevel, deepnessLevel, path) => obj.GetType() + Environment.NewLine); var ans = printer.PrintToString(array); @@ -287,13 +316,14 @@ public void Dictionary_ShouldBeSerializedAs_JSON() {3, "three"} }; var printer = ObjectPrinter - .For>(); + .For>() + .WithTypeSerializationStyle((obj, nestingLevel, deepnessLevel, path) => "number"); var ans = printer.PrintToString(dict); var expected = new StringBuilder(); expected.Append("Dictionary`2\r\n"); foreach(var i in dict) - expected.Append("\t" + i.Key + " = " + i.Value + "\r\n"); + expected.Append("\t" + "number" + " = " + i.Value + "\r\n"); ans.Should().Be(expected.ToString()); }