From 167068cb5ea53ad30f42930c3116dd6afcc502b2 Mon Sep 17 00:00:00 2001 From: Vladimir Golubev Date: Thu, 20 Nov 2025 09:43:29 +0500 Subject: [PATCH 01/41] fix: move tests to separate project --- {ObjectPrinting/Tests => Tests/TestEntities}/Person.cs | 0 .../Tests/ObjectPrinterAcceptanceTests.cs | 0 fluent-api.sln | 6 ++++++ 3 files changed, 6 insertions(+) rename {ObjectPrinting/Tests => Tests/TestEntities}/Person.cs (100%) rename {ObjectPrinting => Tests}/Tests/ObjectPrinterAcceptanceTests.cs (100%) diff --git a/ObjectPrinting/Tests/Person.cs b/Tests/TestEntities/Person.cs similarity index 100% rename from ObjectPrinting/Tests/Person.cs rename to Tests/TestEntities/Person.cs diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs b/Tests/Tests/ObjectPrinterAcceptanceTests.cs similarity index 100% rename from ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs rename to Tests/Tests/ObjectPrinterAcceptanceTests.cs diff --git a/fluent-api.sln b/fluent-api.sln index 69c8db9ed..4992958ad 100644 --- a/fluent-api.sln +++ b/fluent-api.sln @@ -13,6 +13,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FluentMapping.Tests", "Samp EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Spectacle", "Samples\Spectacle\Spectacle.csproj", "{EFA9335C-411B-4597-B0B6-5438D1AE04C3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{CF73988E-264C-4C79-9422-298155B0F249}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -35,6 +37,10 @@ Global {EFA9335C-411B-4597-B0B6-5438D1AE04C3}.Debug|Any CPU.Build.0 = Debug|Any CPU {EFA9335C-411B-4597-B0B6-5438D1AE04C3}.Release|Any CPU.ActiveCfg = Release|Any CPU {EFA9335C-411B-4597-B0B6-5438D1AE04C3}.Release|Any CPU.Build.0 = Release|Any CPU + {CF73988E-264C-4C79-9422-298155B0F249}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CF73988E-264C-4C79-9422-298155B0F249}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CF73988E-264C-4C79-9422-298155B0F249}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CF73988E-264C-4C79-9422-298155B0F249}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 7f440b48e569f5afbc06ef979b17335df4a22ced Mon Sep 17 00:00:00 2001 From: Vladimir Golubev Date: Thu, 20 Nov 2025 09:46:58 +0500 Subject: [PATCH 02/41] test: add acceptance test --- Tests/GlobalUsings.cs | 1 + Tests/Tests.csproj | 20 +++++++++++++++ Tests/Tests/ObjectPrinterAcceptanceTests.cs | 27 ++++++++++++--------- 3 files changed, 36 insertions(+), 12 deletions(-) create mode 100644 Tests/GlobalUsings.cs create mode 100644 Tests/Tests.csproj diff --git a/Tests/GlobalUsings.cs b/Tests/GlobalUsings.cs new file mode 100644 index 000000000..cefced496 --- /dev/null +++ b/Tests/GlobalUsings.cs @@ -0,0 +1 @@ +global using NUnit.Framework; \ No newline at end of file diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj new file mode 100644 index 000000000..31bfb18ae --- /dev/null +++ b/Tests/Tests.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + diff --git a/Tests/Tests/ObjectPrinterAcceptanceTests.cs b/Tests/Tests/ObjectPrinterAcceptanceTests.cs index 4c8b2445c..4d9521ad8 100644 --- a/Tests/Tests/ObjectPrinterAcceptanceTests.cs +++ b/Tests/Tests/ObjectPrinterAcceptanceTests.cs @@ -1,4 +1,4 @@ -using NUnit.Framework; +using System.Globalization; namespace ObjectPrinting.Tests { @@ -10,18 +10,21 @@ public void Demo() { var person = new Person { Name = "Alex", Age = 19 }; - var printer = ObjectPrinter.For(); - //1. Исключить из сериализации свойства определенного типа - //2. Указать альтернативный способ сериализации для определенного типа - //3. Для числовых типов указать культуру - //4. Настроить сериализацию конкретного свойства - //5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств) - //6. Исключить из сериализации конкретного свойства - - string s1 = printer.PrintToString(person); + var printer = ObjectPrinter.For() + .Excluding() // 1. Исключить из сериализации свойства определенного типа + .Printing().Using(i => $"Int({i})") // 2. Указать альтернативный способ сериализации для определенного типа + .Printing().Using(CultureInfo.InvariantCulture) // 3. Для числовых типов указать культуру + .Printing(p => p.Name).Using(n => $"very cool name: {n}") // 4. Настроить сериализацию конкретного свойства + .Printing(p => p.Name).TrimToLength(10) // 5. Настроить обрезание строковых свойств + .Excluding(p => p.Age); // 6. Исключить конкретное свойство - //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию - //8. ...с конфигурированием + var s1 = printer.PrintToString(person); + + var s2 = person.PrintToString(); // 7. Синтаксический сахар — метод расширения + + var s3 = person.PrintToString(p => p + .Printing().Using(CultureInfo.GetCultureInfo("ru-RU")) // 8. ...с конфигурированием + ); } } } \ No newline at end of file From d0847d8a47bb06fa0e638aa78bcd2dadf234881e Mon Sep 17 00:00:00 2001 From: Vladimir Golubev Date: Thu, 20 Nov 2025 09:51:59 +0500 Subject: [PATCH 03/41] feat: add method to exclude types --- ObjectPrinting/PrintingConfig.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index a9e082117..5f9cea329 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Text; @@ -6,6 +7,14 @@ namespace ObjectPrinting { public class PrintingConfig { + internal readonly HashSet ExcludedTypes = new(); + + public PrintingConfig Excluding() + { + ExcludedTypes.Add(typeof(TProp)); + return this; + } + public string PrintToString(TOwner obj) { return PrintToString(obj, 0); From f73c2959ced8f33150212afbf7e9955fdb54c2e4 Mon Sep 17 00:00:00 2001 From: Vladimir Golubev Date: Thu, 20 Nov 2025 09:58:13 +0500 Subject: [PATCH 04/41] feat: add method to exclude members --- ObjectPrinting/PrintingConfig.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index 5f9cea329..a0d42e99a 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; +using System.Reflection; using System.Text; namespace ObjectPrinting @@ -8,12 +10,29 @@ namespace ObjectPrinting public class PrintingConfig { internal readonly HashSet ExcludedTypes = new(); + internal readonly HashSet ExcludedMembers = new(); public PrintingConfig Excluding() { ExcludedTypes.Add(typeof(TProp)); return this; } + + public PrintingConfig Excluding(Expression> selector) + { + ExcludedMembers.Add(GetMember(selector)); + return this; + } + + private static MemberInfo GetMember(Expression> selector) + { + if (selector.Body is MemberExpression memberExpression) + { + return memberExpression.Member; + } + + throw new ArgumentException("Selector must refer to a property or a field."); + } public string PrintToString(TOwner obj) { From a1fdee298bd511df29c5f898b76668280bd1ae1a Mon Sep 17 00:00:00 2001 From: Vladimir Golubev Date: Thu, 20 Nov 2025 10:07:54 +0500 Subject: [PATCH 05/41] feat: add support for custom serializers for types --- ObjectPrinting/ObjectPrinter.cs | 2 + ObjectPrinting/PrintingConfig.cs | 69 ----------------- .../PrintingConfigs/PrintingConfig.cs | 74 +++++++++++++++++++ 3 files changed, 76 insertions(+), 69 deletions(-) delete mode 100644 ObjectPrinting/PrintingConfig.cs create mode 100644 ObjectPrinting/PrintingConfigs/PrintingConfig.cs diff --git a/ObjectPrinting/ObjectPrinter.cs b/ObjectPrinting/ObjectPrinter.cs index 3c7867c32..15b509d84 100644 --- a/ObjectPrinting/ObjectPrinter.cs +++ b/ObjectPrinting/ObjectPrinter.cs @@ -1,3 +1,5 @@ +using ObjectPrinting.PrintingConfigs; + namespace ObjectPrinting { public class ObjectPrinter diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs deleted file mode 100644 index a0d42e99a..000000000 --- a/ObjectPrinting/PrintingConfig.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Reflection; -using System.Text; - -namespace ObjectPrinting -{ - public class PrintingConfig - { - internal readonly HashSet ExcludedTypes = new(); - internal readonly HashSet ExcludedMembers = new(); - - public PrintingConfig Excluding() - { - ExcludedTypes.Add(typeof(TProp)); - return this; - } - - public PrintingConfig Excluding(Expression> selector) - { - ExcludedMembers.Add(GetMember(selector)); - return this; - } - - private static MemberInfo GetMember(Expression> selector) - { - if (selector.Body is MemberExpression memberExpression) - { - return memberExpression.Member; - } - - throw new ArgumentException("Selector must refer to a property or a field."); - } - - public string PrintToString(TOwner obj) - { - return PrintToString(obj, 0); - } - - private string PrintToString(object obj, int nestingLevel) - { - //TODO apply configurations - 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(); - } - } -} \ No newline at end of file diff --git a/ObjectPrinting/PrintingConfigs/PrintingConfig.cs b/ObjectPrinting/PrintingConfigs/PrintingConfig.cs new file mode 100644 index 000000000..1a96a2909 --- /dev/null +++ b/ObjectPrinting/PrintingConfigs/PrintingConfig.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; + +namespace ObjectPrinting.PrintingConfigs; + +public class PrintingConfig +{ + internal readonly HashSet ExcludedTypes = new(); + internal readonly HashSet ExcludedMembers = new(); + internal readonly Dictionary> TypeSerializers = new(); + + public PrintingConfig Excluding() + { + ExcludedTypes.Add(typeof(TProp)); + return this; + } + + public PrintingConfig Excluding(Expression> selector) + { + ExcludedMembers.Add(GetMember(selector)); + return this; + } + + private static MemberInfo GetMember(Expression> selector) + { + if (selector.Body is MemberExpression memberExpression) + { + return memberExpression.Member; + } + + throw new ArgumentException("Selector must refer to a property or a field."); + } + + public TypePrintingConfig Printing() + { + return new TypePrintingConfig(this); + } + + public string PrintToString(TOwner obj) + { + return PrintToString(obj, 0); + } + + private string PrintToString(object obj, int nestingLevel) + { + //TODO apply configurations + 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(); + } +} \ No newline at end of file From 2cf2c90cf0f10e54dcaff71ab89d0c4c0f46d6b1 Mon Sep 17 00:00:00 2001 From: Vladimir Golubev Date: Thu, 20 Nov 2025 10:52:06 +0500 Subject: [PATCH 06/41] feat: add support for cultures --- .../TypePrintingConfigExtensions.cs | 15 +++++++++++++++ .../PrintingConfigs/PrintingConfig.cs | 2 ++ .../PrintingConfigs/TypePrintingConfig.cs | 19 +++++++++++++++++++ 3 files changed, 36 insertions(+) create mode 100644 ObjectPrinting/PrintingConfigs/Extensions/TypePrintingConfigExtensions.cs create mode 100644 ObjectPrinting/PrintingConfigs/TypePrintingConfig.cs diff --git a/ObjectPrinting/PrintingConfigs/Extensions/TypePrintingConfigExtensions.cs b/ObjectPrinting/PrintingConfigs/Extensions/TypePrintingConfigExtensions.cs new file mode 100644 index 000000000..5b007e0ea --- /dev/null +++ b/ObjectPrinting/PrintingConfigs/Extensions/TypePrintingConfigExtensions.cs @@ -0,0 +1,15 @@ +using System; +using System.Globalization; + +namespace ObjectPrinting.PrintingConfigs.Extensions; + +public static class TypePrintingConfigExtensions +{ + public static PrintingConfig Using( + this TypePrintingConfig config, CultureInfo cultureInfo) + where TProp : IFormattable + { + config.Config.TypeCultures[typeof(TProp)] = cultureInfo; + return config.Config; + } +} \ No newline at end of file diff --git a/ObjectPrinting/PrintingConfigs/PrintingConfig.cs b/ObjectPrinting/PrintingConfigs/PrintingConfig.cs index 1a96a2909..68e1c17a6 100644 --- a/ObjectPrinting/PrintingConfigs/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfigs/PrintingConfig.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -12,6 +13,7 @@ public class PrintingConfig internal readonly HashSet ExcludedTypes = new(); internal readonly HashSet ExcludedMembers = new(); internal readonly Dictionary> TypeSerializers = new(); + internal readonly Dictionary TypeCultures = new(); public PrintingConfig Excluding() { diff --git a/ObjectPrinting/PrintingConfigs/TypePrintingConfig.cs b/ObjectPrinting/PrintingConfigs/TypePrintingConfig.cs new file mode 100644 index 000000000..46e64dfd1 --- /dev/null +++ b/ObjectPrinting/PrintingConfigs/TypePrintingConfig.cs @@ -0,0 +1,19 @@ +using System; + +namespace ObjectPrinting.PrintingConfigs; + +public class TypePrintingConfig +{ + internal readonly PrintingConfig Config; + + internal TypePrintingConfig(PrintingConfig config) + { + Config = config; + } + + public PrintingConfig Using(Func serializer) + { + Config.TypeSerializers[typeof(TProp)] = p => serializer((TProp)p); + return Config; + } +} \ No newline at end of file From a07751a5912470c8ae4d3a6ca6fb333aa678f424 Mon Sep 17 00:00:00 2001 From: Vladimir Golubev Date: Thu, 20 Nov 2025 11:02:38 +0500 Subject: [PATCH 07/41] feat: add support for custom serializers for members --- .../PrintingConfigs/PrintingConfig.cs | 6 +++++ .../PrintingConfigs/PropertyPrintingConfig.cs | 22 +++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 ObjectPrinting/PrintingConfigs/PropertyPrintingConfig.cs diff --git a/ObjectPrinting/PrintingConfigs/PrintingConfig.cs b/ObjectPrinting/PrintingConfigs/PrintingConfig.cs index 68e1c17a6..2eae9a7f7 100644 --- a/ObjectPrinting/PrintingConfigs/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfigs/PrintingConfig.cs @@ -13,6 +13,7 @@ public class PrintingConfig internal readonly HashSet ExcludedTypes = new(); internal readonly HashSet ExcludedMembers = new(); internal readonly Dictionary> TypeSerializers = new(); + internal readonly Dictionary> MemberSerializers = new(); internal readonly Dictionary TypeCultures = new(); public PrintingConfig Excluding() @@ -41,6 +42,11 @@ public TypePrintingConfig Printing() { return new TypePrintingConfig(this); } + + public PropertyPrintingConfig Printing(Expression> selector) + { + return new PropertyPrintingConfig(this, GetMember(selector)); + } public string PrintToString(TOwner obj) { diff --git a/ObjectPrinting/PrintingConfigs/PropertyPrintingConfig.cs b/ObjectPrinting/PrintingConfigs/PropertyPrintingConfig.cs new file mode 100644 index 000000000..49a574ea6 --- /dev/null +++ b/ObjectPrinting/PrintingConfigs/PropertyPrintingConfig.cs @@ -0,0 +1,22 @@ +using System; +using System.Reflection; + +namespace ObjectPrinting.PrintingConfigs; + +public class PropertyPrintingConfig +{ + private readonly PrintingConfig _config; + private readonly MemberInfo _member; + + public PropertyPrintingConfig(PrintingConfig config, MemberInfo member) + { + _config = config; + _member = member; + } + + public PrintingConfig Using(Func serializer) + { + _config.MemberSerializers[_member] = m => serializer((TProp)m); + return _config; + } +} \ No newline at end of file From a1659b58e115d7154cb2eb851df4467617b61ee1 Mon Sep 17 00:00:00 2001 From: Vladimir Golubev Date: Thu, 20 Nov 2025 11:21:19 +0500 Subject: [PATCH 08/41] feat: add support for trimmed length --- .../PrintingConfigs/PrintingConfig.cs | 6 +++++ .../PrintingConfigs/PropertyPrintingConfig.cs | 12 +++++----- .../StringPropertyPrintingConfig.cs | 23 +++++++++++++++++++ 3 files changed, 35 insertions(+), 6 deletions(-) create mode 100644 ObjectPrinting/PrintingConfigs/StringPropertyPrintingConfig.cs diff --git a/ObjectPrinting/PrintingConfigs/PrintingConfig.cs b/ObjectPrinting/PrintingConfigs/PrintingConfig.cs index 2eae9a7f7..65796566d 100644 --- a/ObjectPrinting/PrintingConfigs/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfigs/PrintingConfig.cs @@ -15,6 +15,7 @@ public class PrintingConfig internal readonly Dictionary> TypeSerializers = new(); internal readonly Dictionary> MemberSerializers = new(); internal readonly Dictionary TypeCultures = new(); + internal readonly Dictionary TrimLengths = new(); public PrintingConfig Excluding() { @@ -47,6 +48,11 @@ public PropertyPrintingConfig Printing(Expression(this, GetMember(selector)); } + + public StringPropertyPrintingConfig Printing(Expression> selector) + { + return new StringPropertyPrintingConfig(this, GetMember(selector)); + } public string PrintToString(TOwner obj) { diff --git a/ObjectPrinting/PrintingConfigs/PropertyPrintingConfig.cs b/ObjectPrinting/PrintingConfigs/PropertyPrintingConfig.cs index 49a574ea6..9a4800d62 100644 --- a/ObjectPrinting/PrintingConfigs/PropertyPrintingConfig.cs +++ b/ObjectPrinting/PrintingConfigs/PropertyPrintingConfig.cs @@ -5,18 +5,18 @@ namespace ObjectPrinting.PrintingConfigs; public class PropertyPrintingConfig { - private readonly PrintingConfig _config; - private readonly MemberInfo _member; + protected readonly PrintingConfig Config; + protected readonly MemberInfo Member; public PropertyPrintingConfig(PrintingConfig config, MemberInfo member) { - _config = config; - _member = member; + Config = config; + Member = member; } public PrintingConfig Using(Func serializer) { - _config.MemberSerializers[_member] = m => serializer((TProp)m); - return _config; + Config.MemberSerializers[Member] = m => serializer((TProp)m); + return Config; } } \ No newline at end of file diff --git a/ObjectPrinting/PrintingConfigs/StringPropertyPrintingConfig.cs b/ObjectPrinting/PrintingConfigs/StringPropertyPrintingConfig.cs new file mode 100644 index 000000000..21ac53a7c --- /dev/null +++ b/ObjectPrinting/PrintingConfigs/StringPropertyPrintingConfig.cs @@ -0,0 +1,23 @@ +using System; +using System.Reflection; + +namespace ObjectPrinting.PrintingConfigs; + +public class StringPropertyPrintingConfig : PropertyPrintingConfig +{ + public StringPropertyPrintingConfig(PrintingConfig config, MemberInfo member) : base(config, member) + { + } + + public PrintingConfig TrimToLength(int maxLength) + { + if (maxLength < 0) + { + throw new ArgumentException("Max length cannot be less than zero."); + } + + Config.TrimLengths[Member] = maxLength; + + return Config; + } +} \ No newline at end of file From f3db9692473568339b5e732d6b75be1f167505c1 Mon Sep 17 00:00:00 2001 From: Vladimir Golubev Date: Thu, 20 Nov 2025 13:56:51 +0500 Subject: [PATCH 09/41] feat: add printToString realization --- .../PrintingConfigs/PrintingConfig.cs | 198 +++++++++++++++--- ObjectPrinting/Solved/ObjectExtensions.cs | 10 - ObjectPrinting/Solved/ObjectPrinter.cs | 10 - ObjectPrinting/Solved/PrintingConfig.cs | 62 ------ .../Solved/PropertyPrintingConfig.cs | 32 --- .../PropertyPrintingConfigExtensions.cs | 18 -- .../Tests/ObjectPrinterAcceptanceTests.cs | 40 ---- ObjectPrinting/Solved/Tests/Person.cs | 12 -- Tests/TestEntities/Person.cs | 4 +- Tests/Tests.csproj | 4 + Tests/Tests/ObjectPrinterAcceptanceTests.cs | 10 +- 11 files changed, 182 insertions(+), 218 deletions(-) delete mode 100644 ObjectPrinting/Solved/ObjectExtensions.cs delete mode 100644 ObjectPrinting/Solved/ObjectPrinter.cs delete mode 100644 ObjectPrinting/Solved/PrintingConfig.cs delete mode 100644 ObjectPrinting/Solved/PropertyPrintingConfig.cs delete mode 100644 ObjectPrinting/Solved/PropertyPrintingConfigExtensions.cs delete mode 100644 ObjectPrinting/Solved/Tests/ObjectPrinterAcceptanceTests.cs delete mode 100644 ObjectPrinting/Solved/Tests/Person.cs diff --git a/ObjectPrinting/PrintingConfigs/PrintingConfig.cs b/ObjectPrinting/PrintingConfigs/PrintingConfig.cs index 65796566d..28ffb857e 100644 --- a/ObjectPrinting/PrintingConfigs/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfigs/PrintingConfig.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -29,16 +30,6 @@ public PrintingConfig Excluding(Expression> s return this; } - private static MemberInfo GetMember(Expression> selector) - { - if (selector.Body is MemberExpression memberExpression) - { - return memberExpression.Member; - } - - throw new ArgumentException("Selector must refer to a property or a field."); - } - public TypePrintingConfig Printing() { return new TypePrintingConfig(this); @@ -48,41 +39,192 @@ public PropertyPrintingConfig Printing(Expression(this, GetMember(selector)); } - + public StringPropertyPrintingConfig Printing(Expression> selector) { return new StringPropertyPrintingConfig(this, GetMember(selector)); } - + public string PrintToString(TOwner obj) { - return PrintToString(obj, 0); + return PrintToString(obj, 0, new HashSet()); } - private string PrintToString(object obj, int nestingLevel) + private string PrintToString(object? obj, int nestingLevel, HashSet visited) { - //TODO apply configurations - if (obj == null) + if (obj is null) + { return "null" + Environment.NewLine; + } - var finalTypes = new[] + var type = obj.GetType(); + + if (ExcludedTypes.Contains(type)) { - typeof(int), typeof(double), typeof(float), typeof(string), - typeof(DateTime), typeof(TimeSpan) - }; - if (finalTypes.Contains(obj.GetType())) - return obj + Environment.NewLine; + return string.Empty; + } - var identation = new string('\t', nestingLevel + 1); - var sb = new StringBuilder(); + if (!type.IsValueType) + { + if (visited.Contains(obj)) + { + return "(cyclic reference)" + Environment.NewLine; + } + + visited.Add(obj); + } + + if (TypeSerializers.TryGetValue(type, out var typeSerializer)) + { + return typeSerializer(obj) + Environment.NewLine; + } + + if (obj is IFormattable) + { + if (TypeCultures.TryGetValue(type, out var culture)) + { + return Convert.ToString(obj, culture) + Environment.NewLine; + } + } + + if (IsFinalType(type)) + { + return obj.ToString(); + } + + if (obj is IDictionary dictionary) + { + return PrintDictionary(dictionary, nestingLevel, visited); + } + + if (obj is IEnumerable enumerable) + { + return PrintEnumerable(enumerable, nestingLevel, visited); + } + + return PrintObject(obj, nestingLevel, visited); + } + + private string PrintObject(object obj, int nestingLevel, HashSet visited) + { var type = obj.GetType(); + var ident = new string('\t', nestingLevel + 1); + var sb = new StringBuilder(); + sb.AppendLine(type.Name); - foreach (var propertyInfo in type.GetProperties()) + + var members = type.GetProperties() + .Concat(type.GetFields().Cast()); + + foreach (var member in members) { - sb.Append(identation + propertyInfo.Name + " = " + - PrintToString(propertyInfo.GetValue(obj), - nestingLevel + 1)); + if (ExcludedMembers.Contains(member)) + { + continue; + } + + sb.Append(ident + member.Name + " = "); + + var value = GetMemberValue(obj, member); + + if (MemberSerializers.TryGetValue(member, out var memberSerializer)) + { + sb.Append(memberSerializer(value)).AppendLine(); + } + + if (value is string stringValue && TrimLengths.TryGetValue(member, out int trimLength)) + { + if (stringValue.Length > trimLength) + { + sb.Append(stringValue.Substring(0, trimLength)); + } + } + + sb.Append(PrintToString(FormatMemberValue(obj, member), nestingLevel + 1, visited)); } + return sb.ToString(); } + + private object? FormatMemberValue(object value, MemberInfo memberInfo) + { + if (value is string stringValue && TrimLengths.TryGetValue(memberInfo, out int trimLength)) + { + if (stringValue.Length > trimLength) + { + return stringValue.Substring(0, trimLength); + } + } + + return value; + } + + private string PrintEnumerable(IEnumerable enumerable, int nestingLevel, HashSet visited) + { + var indent = new string('\t', nestingLevel + 1); + var sb = new StringBuilder(); + + sb.AppendLine("["); + + foreach (var item in enumerable) + { + sb.Append(indent) + .Append(PrintToString(item, nestingLevel + 1, visited)); + } + + sb.Append(new string('\t', nestingLevel)).AppendLine("]"); + return sb.ToString(); + } + + private string PrintDictionary(IDictionary dict, int nesting, HashSet visited) + { + var indent = new string('\t', nesting + 1); + var sb = new StringBuilder(); + + sb.AppendLine("{"); + + foreach (DictionaryEntry entry in dict) + { + var key = entry.Key; + var value = entry.Value; + + sb.Append(indent + "["); + sb.Append(PrintToString(key, nesting + 1, visited).TrimEnd()); + sb.Append("] = "); + sb.Append(PrintToString(value, nesting + 1, visited)); + } + + sb.Append(new string('\t', nesting)).AppendLine("}"); + return sb.ToString(); + } + + private static bool IsFinalType(Type type) + { + return type.IsPrimitive + || type == typeof(string) + || type == typeof(DateTime) + || type == typeof(TimeSpan) + || type == typeof(decimal); + } + + + private static object? GetMemberValue(object? obj, MemberInfo member) + { + return member switch + { + PropertyInfo p => p.GetValue(obj), + FieldInfo f => f.GetValue(obj), + _ => throw new InvalidOperationException($"Unsupported member type: {member.MemberType}") + }; + } + + private static MemberInfo GetMember(Expression> selector) + { + if (selector.Body is MemberExpression memberExpression) + { + return memberExpression.Member; + } + + throw new ArgumentException("Selector must refer to a property or a field."); + } } \ No newline at end of file diff --git a/ObjectPrinting/Solved/ObjectExtensions.cs b/ObjectPrinting/Solved/ObjectExtensions.cs deleted file mode 100644 index b0c94553c..000000000 --- a/ObjectPrinting/Solved/ObjectExtensions.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace ObjectPrinting.Solved -{ - public static class ObjectExtensions - { - public static string PrintToString(this T obj) - { - return ObjectPrinter.For().PrintToString(obj); - } - } -} \ No newline at end of file diff --git a/ObjectPrinting/Solved/ObjectPrinter.cs b/ObjectPrinting/Solved/ObjectPrinter.cs deleted file mode 100644 index 540ee769c..000000000 --- a/ObjectPrinting/Solved/ObjectPrinter.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace ObjectPrinting.Solved -{ - public class ObjectPrinter - { - public static PrintingConfig For() - { - return new PrintingConfig(); - } - } -} \ No newline at end of file diff --git a/ObjectPrinting/Solved/PrintingConfig.cs b/ObjectPrinting/Solved/PrintingConfig.cs deleted file mode 100644 index 0ec5aeb2b..000000000 --- a/ObjectPrinting/Solved/PrintingConfig.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Linq; -using System.Linq.Expressions; -using System.Text; - -namespace ObjectPrinting.Solved -{ - public class PrintingConfig - { - public PropertyPrintingConfig Printing() - { - return new PropertyPrintingConfig(this); - } - - public PropertyPrintingConfig Printing(Expression> memberSelector) - { - return new PropertyPrintingConfig(this); - } - - public PrintingConfig Excluding(Expression> memberSelector) - { - return this; - } - - internal PrintingConfig Excluding() - { - return this; - } - - public string PrintToString(TOwner obj) - { - return PrintToString(obj, 0); - } - - private string PrintToString(object obj, int nestingLevel) - { - //TODO apply configurations - 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(); - } - } -} \ No newline at end of file diff --git a/ObjectPrinting/Solved/PropertyPrintingConfig.cs b/ObjectPrinting/Solved/PropertyPrintingConfig.cs deleted file mode 100644 index a509697d1..000000000 --- a/ObjectPrinting/Solved/PropertyPrintingConfig.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Globalization; - -namespace ObjectPrinting.Solved -{ - public class PropertyPrintingConfig : IPropertyPrintingConfig - { - private readonly PrintingConfig printingConfig; - - public PropertyPrintingConfig(PrintingConfig printingConfig) - { - this.printingConfig = printingConfig; - } - - public PrintingConfig Using(Func print) - { - return printingConfig; - } - - public PrintingConfig Using(CultureInfo culture) - { - return printingConfig; - } - - PrintingConfig IPropertyPrintingConfig.ParentConfig => printingConfig; - } - - public interface IPropertyPrintingConfig - { - PrintingConfig ParentConfig { get; } - } -} \ No newline at end of file diff --git a/ObjectPrinting/Solved/PropertyPrintingConfigExtensions.cs b/ObjectPrinting/Solved/PropertyPrintingConfigExtensions.cs deleted file mode 100644 index dd3922394..000000000 --- a/ObjectPrinting/Solved/PropertyPrintingConfigExtensions.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; - -namespace ObjectPrinting.Solved -{ - public static class PropertyPrintingConfigExtensions - { - public static string PrintToString(this T obj, Func, PrintingConfig> config) - { - return config(ObjectPrinter.For()).PrintToString(obj); - } - - public static PrintingConfig TrimmedToLength(this PropertyPrintingConfig propConfig, int maxLen) - { - return ((IPropertyPrintingConfig)propConfig).ParentConfig; - } - - } -} \ No newline at end of file diff --git a/ObjectPrinting/Solved/Tests/ObjectPrinterAcceptanceTests.cs b/ObjectPrinting/Solved/Tests/ObjectPrinterAcceptanceTests.cs deleted file mode 100644 index ac52d5ee5..000000000 --- a/ObjectPrinting/Solved/Tests/ObjectPrinterAcceptanceTests.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Globalization; -using NUnit.Framework; - -namespace ObjectPrinting.Solved.Tests -{ - [TestFixture] - public class ObjectPrinterAcceptanceTests - { - [Test] - public void Demo() - { - var person = new Person { Name = "Alex", Age = 19 }; - - var printer = ObjectPrinter.For() - //1. Исключить из сериализации свойства определенного типа - .Excluding() - //2. Указать альтернативный способ сериализации для определенного типа - .Printing().Using(i => i.ToString("X")) - //3. Для числовых типов указать культуру - .Printing().Using(CultureInfo.InvariantCulture) - //4. Настроить сериализацию конкретного свойства - //5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств) - .Printing(p => p.Name).TrimmedToLength(10) - //6. Исключить из сериализации конкретного свойства - .Excluding(p => p.Age); - - string s1 = printer.PrintToString(person); - - //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию - string s2 = person.PrintToString(); - - //8. ...с конфигурированием - string s3 = person.PrintToString(s => s.Excluding(p => p.Age)); - Console.WriteLine(s1); - Console.WriteLine(s2); - Console.WriteLine(s3); - } - } -} \ No newline at end of file diff --git a/ObjectPrinting/Solved/Tests/Person.cs b/ObjectPrinting/Solved/Tests/Person.cs deleted file mode 100644 index 858ebbf8d..000000000 --- a/ObjectPrinting/Solved/Tests/Person.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace ObjectPrinting.Solved.Tests -{ - public class Person - { - public Guid Id { get; set; } - public string Name { get; set; } - public double Height { get; set; } - public int Age { get; set; } - } -} \ No newline at end of file diff --git a/Tests/TestEntities/Person.cs b/Tests/TestEntities/Person.cs index f95559554..8bce77f84 100644 --- a/Tests/TestEntities/Person.cs +++ b/Tests/TestEntities/Person.cs @@ -1,6 +1,4 @@ -using System; - -namespace ObjectPrinting.Tests +namespace Tests.TestEntities { public class Person { diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index 31bfb18ae..c4b4004ac 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -17,4 +17,8 @@ + + + + diff --git a/Tests/Tests/ObjectPrinterAcceptanceTests.cs b/Tests/Tests/ObjectPrinterAcceptanceTests.cs index 4d9521ad8..b8b7f48c0 100644 --- a/Tests/Tests/ObjectPrinterAcceptanceTests.cs +++ b/Tests/Tests/ObjectPrinterAcceptanceTests.cs @@ -1,6 +1,10 @@ using System.Globalization; +using ObjectPrinting; +using ObjectPrinting.PrintingConfigs.Extensions; +using Tests.TestEntities; -namespace ObjectPrinting.Tests + +namespace Tests.Tests { [TestFixture] public class ObjectPrinterAcceptanceTests @@ -12,9 +16,9 @@ public void Demo() var printer = ObjectPrinter.For() .Excluding() // 1. Исключить из сериализации свойства определенного типа - .Printing().Using(i => $"Int({i})") // 2. Указать альтернативный способ сериализации для определенного типа + .Printing().Using(i => $"INT: {i}") // 2. Указать альтернативный способ сериализации для определенного типа .Printing().Using(CultureInfo.InvariantCulture) // 3. Для числовых типов указать культуру - .Printing(p => p.Name).Using(n => $"very cool name: {n}") // 4. Настроить сериализацию конкретного свойства + .Printing(p => p.Age).Using(a => $"AGE : {a}") // 4. Настроить сериализацию конкретного свойства .Printing(p => p.Name).TrimToLength(10) // 5. Настроить обрезание строковых свойств .Excluding(p => p.Age); // 6. Исключить конкретное свойство From 2712df77686417cdb69a7dc02ace21ba287f9d3b Mon Sep 17 00:00:00 2001 From: Vladimir Golubev Date: Thu, 20 Nov 2025 14:02:35 +0500 Subject: [PATCH 10/41] feat: add extension methods for printing objects --- .../Extensions/ObjectExtensions.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 ObjectPrinting/PrintingConfigs/Extensions/ObjectExtensions.cs diff --git a/ObjectPrinting/PrintingConfigs/Extensions/ObjectExtensions.cs b/ObjectPrinting/PrintingConfigs/Extensions/ObjectExtensions.cs new file mode 100644 index 000000000..b0c8f2f8b --- /dev/null +++ b/ObjectPrinting/PrintingConfigs/Extensions/ObjectExtensions.cs @@ -0,0 +1,17 @@ +using System; + +namespace ObjectPrinting.PrintingConfigs.Extensions; + +public static class ObjectExtensions +{ + public static string PrintToString(this T obj) + { + return ObjectPrinter.For().PrintToString(obj); + } + + public static string PrintToString(this T obj, Func, PrintingConfig> config) + { + return config(ObjectPrinter.For()).PrintToString(obj); + } +} + From 108c9db276223e8e87f47788bc3cdef7ebb83819 Mon Sep 17 00:00:00 2001 From: Vladimir Golubev Date: Thu, 20 Nov 2025 16:15:28 +0500 Subject: [PATCH 11/41] test: add test for excluding method --- ObjectPrinting/ObjectPrinting.csproj | 6 -- Tests/TestEntities/TestClass.cs | 8 +++ Tests/Tests.csproj | 8 ++- Tests/Tests/ObjectPrintingTests.cs | 82 ++++++++++++++++++++++++++++ 4 files changed, 95 insertions(+), 9 deletions(-) create mode 100644 Tests/TestEntities/TestClass.cs create mode 100644 Tests/Tests/ObjectPrintingTests.cs diff --git a/ObjectPrinting/ObjectPrinting.csproj b/ObjectPrinting/ObjectPrinting.csproj index c5db392ff..6a9fd13d2 100644 --- a/ObjectPrinting/ObjectPrinting.csproj +++ b/ObjectPrinting/ObjectPrinting.csproj @@ -3,10 +3,4 @@ net8.0 enable - - - - - - diff --git a/Tests/TestEntities/TestClass.cs b/Tests/TestEntities/TestClass.cs new file mode 100644 index 000000000..7fe830d0d --- /dev/null +++ b/Tests/TestEntities/TestClass.cs @@ -0,0 +1,8 @@ +namespace Tests.TestEntities; + + +public class TestClass +{ + public string Property { get; set; } + public string Field; +} diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj index c4b4004ac..4876d048d 100644 --- a/Tests/Tests.csproj +++ b/Tests/Tests.csproj @@ -10,11 +10,13 @@ - - - + + + + + diff --git a/Tests/Tests/ObjectPrintingTests.cs b/Tests/Tests/ObjectPrintingTests.cs new file mode 100644 index 000000000..c49ac70ce --- /dev/null +++ b/Tests/Tests/ObjectPrintingTests.cs @@ -0,0 +1,82 @@ +using FluentAssertions; +using ObjectPrinting; +using ObjectPrinting.PrintingConfigs; +using ObjectPrinting.PrintingConfigs.Extensions; +using Tests.TestEntities; + +namespace Tests.Tests; + +public class ObjectPrintingTests +{ + private Person person; + + [SetUp] + public void SetUp() + { + person = new Person { Name = "Alex", Age = 19, Height = 175.123 }; + } + + [Test] + public async Task PrintToString_SimpleObject_ShouldSerializeAllMembers() + { + var result = person.PrintToString(); + + await Verify(result); + } + + [Test] + public void PrintToString_ExcludedType_ShouldNotSerializeMembersOfThatType() + { + var printer = new PrintingConfig() + .Excluding(); + + var result = printer.PrintToString(person); + + result.Should().NotContain("Height"); + } + + [Test] + public void PrintToString_ExcludedType_ShouldNotSerializeAllMembersOfThatType() + { + var obj = new TestClass { Property = "Prop", Field = "Field" }; + + var printer = ObjectPrinter.For() + .Excluding(); + + var result = printer.PrintToString(obj); + result.Should().NotContain("Prop").And.NotContain("Field"); + } + + [Test] + public void PrintToString_ExcludedProperty_ShouldNotSerializeProperty() + { + var printer = new PrintingConfig() + .Excluding(p => p.Age); + + var result = printer.PrintToString(person); + + result.Should().NotContain("Age"); + } + + [Test] + public void PrintToString_ExcludedField_ShouldNotSerializeField() + { + var obj = new TestClass { Property = "Prop", Field = "Field" }; + + var result = obj.PrintToString(c => c.Excluding(o => o.Field)); + + result.Should().Contain("Property").And.NotContain("Field"); + } + + [Test] + public void PrintToString_ExcludedMultipleMembers_ShouldNotSerializeMultipleMembers() + { + var printer = ObjectPrinter.For() + .Excluding(p => p.Id) + .Excluding(p => p.Age); + + + var result = printer.PrintToString(person); + result.Should().NotContain("Id").And.NotContain("Age"); + } +} \ No newline at end of file From 82f66931663663b3b981acbcc0602cf3ba3ef701 Mon Sep 17 00:00:00 2001 From: Vladimir Golubev Date: Thu, 20 Nov 2025 16:35:14 +0500 Subject: [PATCH 12/41] feat: add recursionDepth limit --- .../PrintingConfigs/PrintingConfig.cs | 67 +++++++++++++------ 1 file changed, 45 insertions(+), 22 deletions(-) diff --git a/ObjectPrinting/PrintingConfigs/PrintingConfig.cs b/ObjectPrinting/PrintingConfigs/PrintingConfig.cs index 28ffb857e..b9a811143 100644 --- a/ObjectPrinting/PrintingConfigs/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfigs/PrintingConfig.cs @@ -11,6 +11,8 @@ namespace ObjectPrinting.PrintingConfigs; public class PrintingConfig { + private int maxRecursionDepth = 16; + internal readonly HashSet ExcludedTypes = new(); internal readonly HashSet ExcludedMembers = new(); internal readonly Dictionary> TypeSerializers = new(); @@ -18,6 +20,16 @@ public class PrintingConfig internal readonly Dictionary TypeCultures = new(); internal readonly Dictionary TrimLengths = new(); + public PrintingConfig SetMaxRecursionDepth(int recursionDepth) + { + if (recursionDepth <= 0) + { + throw new ArgumentException("Max recursion depth must be greater than zero."); + } + maxRecursionDepth = recursionDepth; + return this; + } + public PrintingConfig Excluding() { ExcludedTypes.Add(typeof(TProp)); @@ -52,6 +64,11 @@ public string PrintToString(TOwner obj) private string PrintToString(object? obj, int nestingLevel, HashSet visited) { + if (nestingLevel > maxRecursionDepth) + { + return string.Empty; + } + if (obj is null) { return "null" + Environment.NewLine; @@ -89,7 +106,7 @@ private string PrintToString(object? obj, int nestingLevel, HashSet visi if (IsFinalType(type)) { - return obj.ToString(); + return obj + Environment.NewLine; } if (obj is IDictionary dictionary) @@ -108,7 +125,7 @@ private string PrintToString(object? obj, int nestingLevel, HashSet visi private string PrintObject(object obj, int nestingLevel, HashSet visited) { var type = obj.GetType(); - var ident = new string('\t', nestingLevel + 1); + var indent = new string('\t', nestingLevel + 1); var sb = new StringBuilder(); sb.AppendLine(type.Name); @@ -119,33 +136,39 @@ private string PrintObject(object obj, int nestingLevel, HashSet visited foreach (var member in members) { if (ExcludedMembers.Contains(member)) - { continue; - } - - sb.Append(ident + member.Name + " = "); var value = GetMemberValue(obj, member); + + if(value is not null && ExcludedTypes.Contains(value.GetType())) + { + continue; + } + sb.Append(indent + member.Name + " = "); + if (MemberSerializers.TryGetValue(member, out var memberSerializer)) { sb.Append(memberSerializer(value)).AppendLine(); + continue; } if (value is string stringValue && TrimLengths.TryGetValue(member, out int trimLength)) { - if (stringValue.Length > trimLength) - { - sb.Append(stringValue.Substring(0, trimLength)); - } + sb.Append(stringValue.Length > trimLength + ? stringValue.Substring(0, trimLength) + : stringValue) + .AppendLine(); + continue; } - sb.Append(PrintToString(FormatMemberValue(obj, member), nestingLevel + 1, visited)); + sb.Append(PrintToString(value, nestingLevel + 1, visited)); } return sb.ToString(); } + private object? FormatMemberValue(object value, MemberInfo memberInfo) { if (value is string stringValue && TrimLengths.TryGetValue(memberInfo, out int trimLength)) @@ -204,10 +227,20 @@ private static bool IsFinalType(Type type) || type == typeof(string) || type == typeof(DateTime) || type == typeof(TimeSpan) - || type == typeof(decimal); + || type == typeof(decimal) + || type == typeof(Guid); } + private static MemberInfo GetMember(Expression> selector) + { + if (selector.Body is MemberExpression memberExpression) + { + return memberExpression.Member; + } + throw new ArgumentException("Selector must refer to a property or a field."); + } + private static object? GetMemberValue(object? obj, MemberInfo member) { return member switch @@ -217,14 +250,4 @@ private static bool IsFinalType(Type type) _ => throw new InvalidOperationException($"Unsupported member type: {member.MemberType}") }; } - - private static MemberInfo GetMember(Expression> selector) - { - if (selector.Body is MemberExpression memberExpression) - { - return memberExpression.Member; - } - - throw new ArgumentException("Selector must refer to a property or a field."); - } } \ No newline at end of file From abb4d8185908c2fdadaacd62184b2b608a971315 Mon Sep 17 00:00:00 2001 From: Vladimir Golubev Date: Thu, 20 Nov 2025 19:20:05 +0500 Subject: [PATCH 13/41] fix: remove unused method --- .../PrintingConfigs/PrintingConfig.cs | 24 ++++--------------- fluent-api.sln.DotSettings | 3 +++ 2 files changed, 8 insertions(+), 19 deletions(-) diff --git a/ObjectPrinting/PrintingConfigs/PrintingConfig.cs b/ObjectPrinting/PrintingConfigs/PrintingConfig.cs index b9a811143..8d6272ccb 100644 --- a/ObjectPrinting/PrintingConfigs/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfigs/PrintingConfig.cs @@ -139,20 +139,20 @@ private string PrintObject(object obj, int nestingLevel, HashSet visited continue; var value = GetMemberValue(obj, member); - - if(value is not null && ExcludedTypes.Contains(value.GetType())) + + if (value is not null && ExcludedTypes.Contains(value.GetType())) { continue; } sb.Append(indent + member.Name + " = "); - + if (MemberSerializers.TryGetValue(member, out var memberSerializer)) { sb.Append(memberSerializer(value)).AppendLine(); continue; } - + if (value is string stringValue && TrimLengths.TryGetValue(member, out int trimLength)) { sb.Append(stringValue.Length > trimLength @@ -161,27 +161,13 @@ private string PrintObject(object obj, int nestingLevel, HashSet visited .AppendLine(); continue; } - + sb.Append(PrintToString(value, nestingLevel + 1, visited)); } return sb.ToString(); } - - private object? FormatMemberValue(object value, MemberInfo memberInfo) - { - if (value is string stringValue && TrimLengths.TryGetValue(memberInfo, out int trimLength)) - { - if (stringValue.Length > trimLength) - { - return stringValue.Substring(0, trimLength); - } - } - - return value; - } - private string PrintEnumerable(IEnumerable enumerable, int nestingLevel, HashSet visited) { var indent = new string('\t', nestingLevel + 1); 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 18057370c3adc792fb0fc5cd6b1ce97a928f5e97 Mon Sep 17 00:00:00 2001 From: Vladimir Golubev Date: Thu, 20 Nov 2025 19:20:33 +0500 Subject: [PATCH 14/41] feat: add test entites --- Tests/TestEntities/Node.cs | 30 +++++++++++++++++++++++++ Tests/TestEntities/SuperComplexClass.cs | 19 ++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 Tests/TestEntities/Node.cs create mode 100644 Tests/TestEntities/SuperComplexClass.cs diff --git a/Tests/TestEntities/Node.cs b/Tests/TestEntities/Node.cs new file mode 100644 index 000000000..d9c0fb404 --- /dev/null +++ b/Tests/TestEntities/Node.cs @@ -0,0 +1,30 @@ +namespace Tests.TestEntities; + +public class Node +{ + public string Value { get; set; } + public Node? Next { get; set; } + + public static Node CreateChain(int length) + { + var parent = new Node { Value = "level0" }; + var node = parent; + + for (int i = 0; i < length; i++) + { + var nextNode = new Node { Value = $"level{i + 1}"}; + node.Next = nextNode; + node = nextNode; + } + return parent; + } + + public static Node CreateChainWithCycle() + { + var node1 = new Node { Value = "0" }; + var node2 = new Node { Value = "1" }; + node1.Next = node2; + node2.Next = node1; + return node1; + } +} \ No newline at end of file diff --git a/Tests/TestEntities/SuperComplexClass.cs b/Tests/TestEntities/SuperComplexClass.cs new file mode 100644 index 000000000..94af9990c --- /dev/null +++ b/Tests/TestEntities/SuperComplexClass.cs @@ -0,0 +1,19 @@ +namespace Tests.TestEntities; + +public class SuperComplexClass +{ + public string ShortName { get; set; } + public string VeryLongDescription { get; set; } + public int Age { get; set; } + public double Weight { get; set; } + public decimal Salary { get; set; } + public decimal Bonus { get; set; } + public DateTime BirthDate { get; set; } + public Guid UserId { get; set; } + public bool IsActive { get; set; } + public List Tags { get; set; } = new(); + public Dictionary Scores { get; set; } = new(); + public SuperComplexClass Manager { get; set; } + public SuperComplexClass Assistant { get; set; } + public List TeamMembers { get; set; } = new(); +} \ No newline at end of file From aa26a5fbd4d9016dd9752f60cabfd57c40bd863d Mon Sep 17 00:00:00 2001 From: Vladimir Golubev Date: Thu, 20 Nov 2025 19:20:46 +0500 Subject: [PATCH 15/41] test: add collection tests --- ...ts.CollectionTests.CollectionTestsClass.cs | 14 +++ .../ObjectPrintingTests.CollectionTests.cs | 105 ++++++++++++++++++ 2 files changed, 119 insertions(+) create mode 100644 Tests/Tests/ObjectPrintingTests.CollectionTests.CollectionTestsClass.cs create mode 100644 Tests/Tests/ObjectPrintingTests.CollectionTests.cs diff --git a/Tests/Tests/ObjectPrintingTests.CollectionTests.CollectionTestsClass.cs b/Tests/Tests/ObjectPrintingTests.CollectionTests.CollectionTestsClass.cs new file mode 100644 index 000000000..1945e2366 --- /dev/null +++ b/Tests/Tests/ObjectPrintingTests.CollectionTests.CollectionTestsClass.cs @@ -0,0 +1,14 @@ +namespace Tests.Tests; + +public partial class ObjectPrintingTests +{ + public partial class CollectionTests + { + private class CollectionTestsClass + { + public string Name { get; set; } + public int Age { get; set; } + public List Friends { get; set; } = new(); + } + } +} \ No newline at end of file diff --git a/Tests/Tests/ObjectPrintingTests.CollectionTests.cs b/Tests/Tests/ObjectPrintingTests.CollectionTests.cs new file mode 100644 index 000000000..78d5d3663 --- /dev/null +++ b/Tests/Tests/ObjectPrintingTests.CollectionTests.cs @@ -0,0 +1,105 @@ +using System.Text.RegularExpressions; +using FluentAssertions; +using ObjectPrinting; + +namespace Tests.Tests; + +public partial class ObjectPrintingTests +{ + [TestFixture] + public partial class CollectionTests + { + private class Department + { + public string Name { get; set; } + } + + [Test] + public void PrintToString_Array_ShouldPrintEachElement() + { + var data = new[] { 1, 2, 3 }; + var printer = ObjectPrinter.For(); + + var result = printer.PrintToString(data); + + result.Should() + .Contain("[") + .And.Contain("1") + .And.Contain("2") + .And.Contain("3") + .And.Contain("]"); + } + + [Test] + public void PrintToString_ArrayOfObjects_ShouldSerializeEachElement() + { + var data = new[] + { + new CollectionTestsClass { Name = "Alice", Age = 25 }, + new CollectionTestsClass { Name = "Bob", Age = 30 } + }; + var printer = ObjectPrinter.For(); + + var result = printer.PrintToString(data); + + result.Should().Contain("[") + .And.Contain("CollectionTestsClass", Exactly.Twice()) + .And.Contain("]"); + } + + [Test] + public void PrintToString_Dictionary_ShoulPrintKeysAndValues() + { + var dict = new Dictionary + { + [1] = "1", + [2] = "2" + }; + + var printer = ObjectPrinter.For>(); + var result = printer.PrintToString(dict); + + result.Should() + .Contain("[1] = 1") + .And.Contain("[2] = 2"); + } + + + [Test] + public void PrintToString_DictionaryWithComplexObjects_ShouldSerializeKeysAndValues() + { + var dict = new Dictionary + { + [new CollectionTestsClass { Name = "Manager" }] = new Department { Name = "HR" }, + [new CollectionTestsClass { Name = "Developer" }] = new Department { Name = "IT" } + }; + + var printer = ObjectPrinter.For>(); + var result = printer.PrintToString(dict); + + result.Should().MatchRegex( + @"\[\s*CollectionTestsClass[\s\S]*?\]\s*=\s*Department[\s\S]*?" + + @"\[\s*CollectionTestsClass[\s\S]*?\]\s*=\s*Department"); + } + + [Test] + public void PrintToString_EmptyCollection_ShouldShowEmptyStructure() + { + var data = new CollectionTestsClass { Name = "Test", Friends = new List() }; + var printer = ObjectPrinter.For(); + var result = printer.PrintToString(data); + + result.Should().Contain("Friends = ["); + } + + [Test] + public void PrintToString_NullCollection_ShouldShowNull() + { + var data = new CollectionTestsClass() { Name = "Test", Friends = null }; + var printer = ObjectPrinter.For(); + var result = printer.PrintToString(data); + + result.Should().Contain("Friends = null"); + } + } +} \ No newline at end of file From b9aa3eba75c182218b707874b4e3678ef1bc973e Mon Sep 17 00:00:00 2001 From: Vladimir Golubev Date: Thu, 20 Nov 2025 19:21:17 +0500 Subject: [PATCH 16/41] test: add printing object tests --- Tests/Tests/ObjectPrintingTests.cs | 110 ++++++++++++++--------------- 1 file changed, 52 insertions(+), 58 deletions(-) diff --git a/Tests/Tests/ObjectPrintingTests.cs b/Tests/Tests/ObjectPrintingTests.cs index c49ac70ce..024c363d5 100644 --- a/Tests/Tests/ObjectPrintingTests.cs +++ b/Tests/Tests/ObjectPrintingTests.cs @@ -1,82 +1,76 @@ -using FluentAssertions; +using System.Globalization; using ObjectPrinting; -using ObjectPrinting.PrintingConfigs; using ObjectPrinting.PrintingConfigs.Extensions; using Tests.TestEntities; namespace Tests.Tests; -public class ObjectPrintingTests +public partial class ObjectPrintingTests { - private Person person; - - [SetUp] - public void SetUp() - { - person = new Person { Name = "Alex", Age = 19, Height = 175.123 }; - } - [Test] public async Task PrintToString_SimpleObject_ShouldSerializeAllMembers() { + var person = new Person { Name = "John", Age = 25 }; var result = person.PrintToString(); await Verify(result); } - - [Test] - public void PrintToString_ExcludedType_ShouldNotSerializeMembersOfThatType() - { - var printer = new PrintingConfig() - .Excluding(); - var result = printer.PrintToString(person); - - result.Should().NotContain("Height"); - } - [Test] - public void PrintToString_ExcludedType_ShouldNotSerializeAllMembersOfThatType() + public async Task PrintToString_WithMaximumConfigurations_ShouldApplyAllCorrectly() { - var obj = new TestClass { Property = "Prop", Field = "Field" }; - - var printer = ObjectPrinter.For() - .Excluding(); - - var result = printer.PrintToString(obj); - result.Should().NotContain("Prop").And.NotContain("Field"); - } - - [Test] - public void PrintToString_ExcludedProperty_ShouldNotSerializeProperty() - { - var printer = new PrintingConfig() - .Excluding(p => p.Age); + var testData = new SuperComplexClass + { + ShortName = "John", + VeryLongDescription = "This is a very long description that should be trimmed because it exceeds the maximum allowed length for this property", + Age = 35, + Weight = 75.5, + Salary = 75000.50m, + Bonus = 15000.25m, + BirthDate = new DateTime(1988, 5, 15, 10, 30, 0), + UserId = Guid.Parse("12345678-1234-1234-1234-123456789012"), + IsActive = true, + Tags = new List { "developer", "senior", "backend", "very-long-tag-that-should-be-trimmed" }, + Scores = new Dictionary + { + ["C#"] = 95, + ["SQL"] = 88, + ["JavaScript"] = 76 + }, + TeamMembers = new List + { + new() { ShortName = "Alice", Age = 28, Salary = 60000.00m }, + new() { ShortName = "Bob", Age = 32, Salary = 65000.00m } + }, + Manager = new SuperComplexClass { ShortName = "Manager", Age = 45 } + }; - var result = printer.PrintToString(person); - - result.Should().NotContain("Age"); - } + testData.Manager.TeamMembers.Add(testData); + testData.Assistant = testData.TeamMembers[0]; - [Test] - public void PrintToString_ExcludedField_ShouldNotSerializeField() - { - var obj = new TestClass { Property = "Prop", Field = "Field" }; + var printer = ObjectPrinter.For() + .Excluding() + .Excluding() + .Excluding(x => x.Weight) + .Excluding(x => x.Bonus) + .Printing().Using(i => $"{i} years") + .Printing().Using(d => $"{d} kg") + .Printing().Using(d => $"{d:C2}") + .Printing().Using(dt => dt.ToString("yyyy-MM-dd")) + .Printing().Using(s => s.ToUpper()) + .Printing(x => x.Salary).Using(s => $"Salary: {s}$") + .Printing(x => x.Age).Using(a => $"Age is {a}") + .Printing(x => x.ShortName).TrimToLength(2) + .Printing(x => x.VeryLongDescription).TrimToLength(20) + .Printing().Using(new CultureInfo("de-DE")) + .Printing().Using(new CultureInfo("fr-FR")) + .Printing>().Using(list => $"Tags[{list.Count}]") + .Printing>().Using(dict => $"Scores[{dict.Count}]"); - var result = obj.PrintToString(c => c.Excluding(o => o.Field)); + var result = printer.PrintToString(testData); - result.Should().Contain("Property").And.NotContain("Field"); + await Verify(result); } - [Test] - public void PrintToString_ExcludedMultipleMembers_ShouldNotSerializeMultipleMembers() - { - var printer = ObjectPrinter.For() - .Excluding(p => p.Id) - .Excluding(p => p.Age); - - - var result = printer.PrintToString(person); - result.Should().NotContain("Id").And.NotContain("Age"); - } + } \ No newline at end of file From cada7138d90f2dd1e1e305fafd84f262595efac6 Mon Sep 17 00:00:00 2001 From: Vladimir Golubev Date: Thu, 20 Nov 2025 19:21:37 +0500 Subject: [PATCH 17/41] test: add culture tests --- .../Tests/ObjectPrintingTests.CultureTests.cs | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 Tests/Tests/ObjectPrintingTests.CultureTests.cs diff --git a/Tests/Tests/ObjectPrintingTests.CultureTests.cs b/Tests/Tests/ObjectPrintingTests.CultureTests.cs new file mode 100644 index 000000000..561c350a6 --- /dev/null +++ b/Tests/Tests/ObjectPrintingTests.CultureTests.cs @@ -0,0 +1,60 @@ +using System.Globalization; +using FluentAssertions; +using ObjectPrinting; +using ObjectPrinting.PrintingConfigs.Extensions; + +namespace Tests.Tests; + +public partial class ObjectPrintingTests +{ + [TestFixture] + public class CultureTests + { + public class CultureTestClass + { + public decimal Decimal { get; set; } + public DateTime Date { get; set; } + public double Double { get; set; } + } + + private CultureTestClass testData; + + [SetUp] + public void SetUp() + { + testData = new CultureTestClass + { + Decimal = 1234.56m, + Date = new DateTime(2023, 10, 15, 14, 30, 0), + Double = 1234.567 + }; + } + + [Test] + public void PrintToString_CustomCulture_ShouldApplyFormatting() + { + var printer = ObjectPrinter.For() + .Printing().Using(new CultureInfo("de-DE")); + + var result = printer.PrintToString(testData); + + result.Should().Contain("Decimal = 1234,56"); + } + + [Test] + public void PrintToString_MultipleCultures_ShouldApplyAll() + { + var printer = ObjectPrinter.For() + .Printing().Using(new CultureInfo("es-ES")) + .Printing().Using(new CultureInfo("en-US")) + .Printing().Using(new CultureInfo("fr-FR")); + + var result = printer.PrintToString(testData); + + result.Should() + .Contain("Decimal = 1234.56") + .And.Contain("Date = 15/10/2023 14:30:00") + .And.Contain("Double = 1234,567"); + } + } +} \ No newline at end of file From e72933be0e791c8172fd45213dc8442cd038357b Mon Sep 17 00:00:00 2001 From: Vladimir Golubev Date: Thu, 20 Nov 2025 19:21:51 +0500 Subject: [PATCH 18/41] test: add serializers tests --- ...ectPrintingTests.CustomSerializersTests.cs | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 Tests/Tests/ObjectPrintingTests.CustomSerializersTests.cs diff --git a/Tests/Tests/ObjectPrintingTests.CustomSerializersTests.cs b/Tests/Tests/ObjectPrintingTests.CustomSerializersTests.cs new file mode 100644 index 000000000..d0539317e --- /dev/null +++ b/Tests/Tests/ObjectPrintingTests.CustomSerializersTests.cs @@ -0,0 +1,131 @@ +using System.Globalization; +using FluentAssertions; +using ObjectPrinting; +using ObjectPrinting.PrintingConfigs.Extensions; + +namespace Tests.Tests; + +public partial class ObjectPrintingTests +{ + [TestFixture] + public class CustomSerializerTests + { + private class CustomSerializerTestsClass + { + public Guid Id { get; set; } + public string Name { get; set; } + public int Age { get; set; } + public decimal Price { get; set; } + public decimal Discount { get; set; } + } + + private CustomSerializerTestsClass testData; + + [SetUp] + public void SetUp() + { + testData = new CustomSerializerTestsClass + { + Id = Guid.Parse("a1b2c3d4-e5f6-7890-abcd-ef1234567890"), + Name = "John", + Age = 25, + Price = 19.99m, + Discount = 0.1m + }; + } + + [Test] + public void PrintToString_CustomTypeSerializer_ShouldFormatType() + { + var printer = ObjectPrinter.For() + .Printing().Using(g => g + .ToString("N").Substring(0, 8).ToUpper()); + + var result = printer.PrintToString(testData); + + result.Should().Contain("Id = A1B2C3D4"); + } + + [Test] + public void PrintToString_CustomPropertySerializer_ShouldOverrideTypeSerializer() + { + var printer = ObjectPrinter.For() + .Printing().Using(i => $"{i} years") + .Printing(p => p.Age).Using(age => $"Age: {age}"); + + var result = printer.PrintToString(testData); + + result.Should().Contain("Age = Age: 25") + .And.NotContain("Age = 25 years"); + } + + [Test] + public void PrintToString_CustomSerializerReturnsNull_ShouldHandleCorrectly() + { + var printer = ObjectPrinter.For() + .Printing(p => p.Name).Using(_ => null); + + var result = printer.PrintToString(testData); + + result.Should().Contain("Name = "); + } + + [Test] + public void PrintToString_CustomSerializerForMultipleTypes_ShouldApplyAll() + { + var printer = ObjectPrinter.For() + .Printing().Using(g => g.ToString().Substring(0, 8)) + .Printing().Using(i => $"#{i}") + .Printing().Using(p => $"${p}"); + + var result = printer.PrintToString(testData); + + result.Should() + .Contain("Id = a1b2c3d4") + .And.Contain("Age = #25") + .And.Contain("Price = $19,99"); + } + + [Test] + public void PrintToString_CustomSerializerForOneProperty_ShouldNotAffectOtherProperties() + { + var printer = ObjectPrinter.For() + .Printing(p => p.Price).Using(p => $"${p}"); + + var result = printer.PrintToString(testData); + + result.Should().Contain("Price = $19,99") + .And.Contain("Discount = 0,1") + .And.Contain("Name = John"); + } + + [Test] + public void PrintToString_MultiplePropertySerializers_ShouldApplyEachToRespectiveProperty() + { + var printer = ObjectPrinter.For() + .Printing(p => p.Name).Using(n => $"{n.ToUpper()}") + .Printing(p => p.Age).Using(a => $"{a} years") + .Printing(p => p.Price).Using(p => $"${p}"); + + var result = printer.PrintToString(testData); + + result.Should().Contain("Name = JOHN") + .And.Contain("Age = 25 years") + .And.Contain("Price = $19,99"); + } + + [Test] + public void PrintToString_CustomSerializerDominatesOverCulture() + { + var data = new { Price = 1234.56d, Weight = 7.89d }; + var printer = ObjectPrinter.For() + .Printing().Using(new CultureInfo("de-DE")) + .Printing().Using(p => $"${p:0.00}"); + + var result = printer.PrintToString(data); + + result.Should().Contain("Price = $1234,56") + .And.Contain("Weight = $7,89"); + } + } +} \ No newline at end of file From a39cb1d0eef28bda86deca45f005c98c3e04e1ec Mon Sep 17 00:00:00 2001 From: Vladimir Golubev Date: Thu, 20 Nov 2025 19:22:04 +0500 Subject: [PATCH 19/41] test: add excluded tests --- .../ObjectPrintingTests.ExcludingTests.cs | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 Tests/Tests/ObjectPrintingTests.ExcludingTests.cs diff --git a/Tests/Tests/ObjectPrintingTests.ExcludingTests.cs b/Tests/Tests/ObjectPrintingTests.ExcludingTests.cs new file mode 100644 index 000000000..e5b7ec76b --- /dev/null +++ b/Tests/Tests/ObjectPrintingTests.ExcludingTests.cs @@ -0,0 +1,103 @@ +using FluentAssertions; +using ObjectPrinting; +using ObjectPrinting.PrintingConfigs; +using ObjectPrinting.PrintingConfigs.Extensions; +using Tests.TestEntities; + +namespace Tests.Tests; + +public partial class ObjectPrintingTests +{ + [TestFixture] + public class ExcludingTests + { + private Person person; + + [SetUp] + public void SetUp() + { + person = new Person { Name = "Alex", Age = 19, Height = 175.123 }; + } + + + [Test] + public void PrintToString_ExcludedType_ShouldNotSerializeMembersOfThatType() + { + var printer = new PrintingConfig() + .Excluding(); + + var result = printer.PrintToString(person); + + result.Should().NotContain("Height"); + } + + [Test] + public void PrintToString_ExcludedType_ShouldNotSerializeAllMembersOfThatType() + { + var obj = new TestClass { Property = "Prop", Field = "Field" }; + + var printer = ObjectPrinter.For() + .Excluding(); + + var result = printer.PrintToString(obj); + result.Should().NotContain("Prop").And.NotContain("Field"); + } + + [Test] + public void PrintToString_ExcludedProperty_ShouldNotSerializeProperty() + { + var printer = new PrintingConfig() + .Excluding(p => p.Age); + + var result = printer.PrintToString(person); + + result.Should().NotContain("Age"); + } + + [Test] + public void PrintToString_ExcludedField_ShouldNotSerializeField() + { + var obj = new TestClass { Property = "Prop", Field = "Field" }; + + var result = obj.PrintToString(c => c.Excluding(o => o.Field)); + + result.Should().Contain("Property").And.NotContain("Field"); + } + + [Test] + public void PrintToString_ExcludedMultipleMembers_ShouldNotSerializeMultipleMembers() + { + var printer = ObjectPrinter.For() + .Excluding(p => p.Id) + .Excluding(p => p.Age); + + + var result = printer.PrintToString(person); + result.Should().NotContain("Id").And.NotContain("Age"); + } + + [Test] + public void Excluding_WhenNotMemberProvided_ShouldThrowArgumentException() + { + var printer = ObjectPrinter.For(); + + var act = () => printer.Excluding(p => p.GetHashCode()); + + act.Should().Throw(); + } + + [Test] + public void PrintToString_AllPropertiesExcluded_ShouldShowTypeName() + { + var printer = ObjectPrinter.For() + .Excluding() + .Excluding() + .Excluding(p => p.Id) + .Excluding(p => p.Height); + + var result = printer.PrintToString(person); + + result.Should().Be("Person" + Environment.NewLine); + } + } +} \ No newline at end of file From 2a72e205d91cfa167cd91d9a4d6554dad4f615c5 Mon Sep 17 00:00:00 2001 From: Vladimir Golubev Date: Thu, 20 Nov 2025 19:22:18 +0500 Subject: [PATCH 20/41] test: add nested objects tests --- .../ObjectPrintingTests.NestedObjectsTest.cs | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 Tests/Tests/ObjectPrintingTests.NestedObjectsTest.cs diff --git a/Tests/Tests/ObjectPrintingTests.NestedObjectsTest.cs b/Tests/Tests/ObjectPrintingTests.NestedObjectsTest.cs new file mode 100644 index 000000000..fb6f912ca --- /dev/null +++ b/Tests/Tests/ObjectPrintingTests.NestedObjectsTest.cs @@ -0,0 +1,62 @@ +using FluentAssertions; +using ObjectPrinting; +using Tests.TestEntities; + +namespace Tests.Tests; + +public partial class ObjectPrintingTests +{ + [TestFixture] + public class NestedObjectsTest + { + [Test] + public void PrintToString_NestedObject_ShouldPrintAllLevels() + { + var node = Node.CreateChain(3); + var printer = ObjectPrinter.For(); + + var result = printer.PrintToString(node); + + result.Should().Contain("level0") + .And.Contain("level1") + .And.Contain("level2"); + } + + [Test] + public void PrintToString_WithCyclicReference_ShouldNotFall() + { + var node = Node.CreateChainWithCycle(); + var printer = ObjectPrinter.For(); + + var act = () => printer.PrintToString(node); + + act.Should().NotThrow(); + act().Should().Contain("cyclic reference"); + } + + [Test] + public void PrintToString_WhenReachesMaxRecursionLevel_ShouldStop() + { + var node = Node.CreateChain(16); + var printer = ObjectPrinter.For(); + + var result = printer.PrintToString(node); + + result.Should() + .Contain("level15") + .And.NotContain("level16"); + } + + [TestCase(0)] + [TestCase(-1)] + public void SetMaxRecursionDepth_NotPositiveValue_ShouldThrowArgumentException(int maxDepth) + { + var printer = ObjectPrinter.For(); + + var act = () => printer.SetMaxRecursionDepth(maxDepth); + + act.Should().Throw(); + } + + } +} \ No newline at end of file From 28bbf2e0dd0cd3edfd954a5c700e9421dc78434a Mon Sep 17 00:00:00 2001 From: Vladimir Golubev Date: Thu, 20 Nov 2025 19:22:28 +0500 Subject: [PATCH 21/41] test: add trim tests --- Tests/Tests/ObjectPrintingTests.TrimTests.cs | 63 ++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 Tests/Tests/ObjectPrintingTests.TrimTests.cs diff --git a/Tests/Tests/ObjectPrintingTests.TrimTests.cs b/Tests/Tests/ObjectPrintingTests.TrimTests.cs new file mode 100644 index 000000000..f97a62ff7 --- /dev/null +++ b/Tests/Tests/ObjectPrintingTests.TrimTests.cs @@ -0,0 +1,63 @@ +using FluentAssertions; +using ObjectPrinting; + +namespace Tests.Tests; + +public partial class ObjectPrintingTests +{ + [TestFixture] + public class TrimTests + { + private class TrimTestsClass + { + public string Short { get; set; } + public string Long { get; set; } + public string Description { get; set; } + } + + [Test] + public void PrintToString_TrimToZeroLength_ShouldReturnEmptyString() + { + var data = new TrimTestsClass { Long = "Very long text" }; + var printer = ObjectPrinter.For() + .Printing(p => p.Long).TrimToLength(0); + + var result = printer.PrintToString(data); + + result.Should().Contain("Long = ") + .And.NotContain("Very"); + } + + [Test] + public void PrintToString_TrimMultipleStringProperties_ShouldTrimEachIndependently() + { + var data = new TrimTestsClass + { + Short = "Short", + Long = "Very long text here", + Description = "Medium description" + }; + var printer = ObjectPrinter.For() + .Printing(p => p.Short).TrimToLength(3) + .Printing(p => p.Long).TrimToLength(10) + .Printing(p => p.Description).TrimToLength(6); + + var result = printer.PrintToString(data); + + result.Should().Contain("Short = Sho") + .And.Contain("Long = Very long ") + .And.Contain("Description = Medium"); + } + + [Test] + public void TrimToLength_NotPositiveValue_ShouldThrowArgumentException() + { + var printer = ObjectPrinter.For(); + + var act = () => printer.Printing(p => p.Short).TrimToLength(-1); + + act.Should().Throw(); + } + } +} + From 61a30cd46c3466cc4c5505bed937e99a6ddc4c10 Mon Sep 17 00:00:00 2001 From: Vladimir Golubev Date: Thu, 20 Nov 2025 19:24:45 +0500 Subject: [PATCH 22/41] fix: remove unused using --- Tests/Tests/ObjectPrintingTests.CollectionTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/Tests/ObjectPrintingTests.CollectionTests.cs b/Tests/Tests/ObjectPrintingTests.CollectionTests.cs index 78d5d3663..fb9da9cec 100644 --- a/Tests/Tests/ObjectPrintingTests.CollectionTests.cs +++ b/Tests/Tests/ObjectPrintingTests.CollectionTests.cs @@ -1,4 +1,3 @@ -using System.Text.RegularExpressions; using FluentAssertions; using ObjectPrinting; From 32108d4bde8a45340f2f294fa4509e998afaf0f0 Mon Sep 17 00:00:00 2001 From: Vladimir Golubev Date: Thu, 20 Nov 2025 19:41:47 +0500 Subject: [PATCH 23/41] fix: refactor PrintingConfig --- .../PrintingConfigs/PrintingConfig.cs | 103 ++++++++++++------ 1 file changed, 70 insertions(+), 33 deletions(-) diff --git a/ObjectPrinting/PrintingConfigs/PrintingConfig.cs b/ObjectPrinting/PrintingConfigs/PrintingConfig.cs index 8d6272ccb..06ab81aee 100644 --- a/ObjectPrinting/PrintingConfigs/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfigs/PrintingConfig.cs @@ -91,38 +91,30 @@ private string PrintToString(object? obj, int nestingLevel, HashSet visi visited.Add(obj); } - if (TypeSerializers.TryGetValue(type, out var typeSerializer)) - { - return typeSerializer(obj) + Environment.NewLine; - } - - if (obj is IFormattable) - { - if (TypeCultures.TryGetValue(type, out var culture)) - { - return Convert.ToString(obj, culture) + Environment.NewLine; - } - } - + return FormatObjectByType(obj, nestingLevel, visited, type); + } + + private string FormatObjectByType(object obj, int nestingLevel, HashSet visited, Type type) + { + if (TrySerializeType(type, obj, out string? result)) + return result + Environment.NewLine; + + if (TryFormatFormattable(obj, type, out result)) + return result; + if (IsFinalType(type)) - { return obj + Environment.NewLine; - } - + if (obj is IDictionary dictionary) - { return PrintDictionary(dictionary, nestingLevel, visited); - } if (obj is IEnumerable enumerable) - { return PrintEnumerable(enumerable, nestingLevel, visited); - } return PrintObject(obj, nestingLevel, visited); } - - private string PrintObject(object obj, int nestingLevel, HashSet visited) + + private string PrintObject(object obj, int nestingLevel, HashSet visited) { var type = obj.GetType(); var indent = new string('\t', nestingLevel + 1); @@ -131,13 +123,11 @@ private string PrintObject(object obj, int nestingLevel, HashSet visited sb.AppendLine(type.Name); var members = type.GetProperties() - .Concat(type.GetFields().Cast()); + .Concat(type.GetFields().Cast()) + .Where(m => !ExcludedMembers.Contains(m)); foreach (var member in members) { - if (ExcludedMembers.Contains(member)) - continue; - var value = GetMemberValue(obj, member); if (value is not null && ExcludedTypes.Contains(value.GetType())) @@ -147,18 +137,15 @@ private string PrintObject(object obj, int nestingLevel, HashSet visited sb.Append(indent + member.Name + " = "); - if (MemberSerializers.TryGetValue(member, out var memberSerializer)) + if (TrySerializeMember(member, value, out var result)) { - sb.Append(memberSerializer(value)).AppendLine(); + sb.Append(result).AppendLine(); continue; } - if (value is string stringValue && TrimLengths.TryGetValue(member, out int trimLength)) + if (TryTrimStringMember(member, value, out var trimmed)) { - sb.Append(stringValue.Length > trimLength - ? stringValue.Substring(0, trimLength) - : stringValue) - .AppendLine(); + sb.Append(trimmed).AppendLine(); continue; } @@ -206,7 +193,57 @@ private string PrintDictionary(IDictionary dict, int nesting, HashSet vi sb.Append(new string('\t', nesting)).AppendLine("}"); return sb.ToString(); } + + private bool TrySerializeType(Type type, object obj, out string? result) + { + if (TypeSerializers.TryGetValue(type, out var serializer)) + { + result = serializer(obj); + return true; + } + + result = null; + return false; + } + + private bool TrySerializeMember(MemberInfo member, object? value, out string? result) + { + if (MemberSerializers.TryGetValue(member, out var serializer)) + { + result = serializer(value); + return true; + } + + result = null; + return false; + } + private bool TryFormatFormattable(object obj, Type type, out string result) + { + if (obj is IFormattable formattable && TypeCultures.TryGetValue(type, out var culture)) + { + result = Convert.ToString(formattable, culture) + Environment.NewLine; + return true; + } + + result = string.Empty; + return false; + } + + private bool TryTrimStringMember(MemberInfo member, object? value, out string? result) + { + if (value is string stringValue && TrimLengths.TryGetValue(member, out int trimLength)) + { + result = stringValue.Length > trimLength + ? stringValue.Substring(0, trimLength) + : stringValue; + return true; + } + + result = null; + return false; + } + private static bool IsFinalType(Type type) { return type.IsPrimitive From 74b9ee1a36c7c019764e0b276b185be283184c39 Mon Sep 17 00:00:00 2001 From: Vladimir Golubev Date: Thu, 20 Nov 2025 21:54:05 +0500 Subject: [PATCH 24/41] fix: now use invariant culture for printing --- ObjectPrinting/PrintingConfigs/PrintingConfig.cs | 2 +- Tests/Tests/ObjectPrintingTests.CustomSerializersTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ObjectPrinting/PrintingConfigs/PrintingConfig.cs b/ObjectPrinting/PrintingConfigs/PrintingConfig.cs index 06ab81aee..fea86b9d8 100644 --- a/ObjectPrinting/PrintingConfigs/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfigs/PrintingConfig.cs @@ -103,7 +103,7 @@ private string FormatObjectByType(object obj, int nestingLevel, HashSet return result; if (IsFinalType(type)) - return obj + Environment.NewLine; + return Convert.ToString(obj, CultureInfo.InvariantCulture) + Environment.NewLine; if (obj is IDictionary dictionary) return PrintDictionary(dictionary, nestingLevel, visited); diff --git a/Tests/Tests/ObjectPrintingTests.CustomSerializersTests.cs b/Tests/Tests/ObjectPrintingTests.CustomSerializersTests.cs index d0539317e..d5ae0d7ec 100644 --- a/Tests/Tests/ObjectPrintingTests.CustomSerializersTests.cs +++ b/Tests/Tests/ObjectPrintingTests.CustomSerializersTests.cs @@ -95,7 +95,7 @@ public void PrintToString_CustomSerializerForOneProperty_ShouldNotAffectOtherPro var result = printer.PrintToString(testData); result.Should().Contain("Price = $19,99") - .And.Contain("Discount = 0,1") + .And.Contain("Discount = 0.1") .And.Contain("Name = John"); } From 1cc1faf1320c6efc62bc01a9578dca7fe252a558 Mon Sep 17 00:00:00 2001 From: Vladimir Golubev Date: Sun, 23 Nov 2025 13:31:29 +0500 Subject: [PATCH 25/41] fix: fix logic with class interaction --- .../TypePrintingConfigExtensions.cs | 3 +- .../PrintingConfigs/PrintingConfig.cs | 68 ++++++++++++++----- .../PrintingConfigs/PropertyPrintingConfig.cs | 10 +-- .../StringPropertyPrintingConfig.cs | 14 ++-- .../PrintingConfigs/TypePrintingConfig.cs | 3 +- 5 files changed, 62 insertions(+), 36 deletions(-) diff --git a/ObjectPrinting/PrintingConfigs/Extensions/TypePrintingConfigExtensions.cs b/ObjectPrinting/PrintingConfigs/Extensions/TypePrintingConfigExtensions.cs index 5b007e0ea..691e8a472 100644 --- a/ObjectPrinting/PrintingConfigs/Extensions/TypePrintingConfigExtensions.cs +++ b/ObjectPrinting/PrintingConfigs/Extensions/TypePrintingConfigExtensions.cs @@ -9,7 +9,6 @@ public static PrintingConfig Using( this TypePrintingConfig config, CultureInfo cultureInfo) where TProp : IFormattable { - config.Config.TypeCultures[typeof(TProp)] = cultureInfo; - return config.Config; + return config.Config.SetCultureFor(cultureInfo); } } \ No newline at end of file diff --git a/ObjectPrinting/PrintingConfigs/PrintingConfig.cs b/ObjectPrinting/PrintingConfigs/PrintingConfig.cs index 06ab81aee..b121a79e5 100644 --- a/ObjectPrinting/PrintingConfigs/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfigs/PrintingConfig.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.Globalization; +using System.IO.Pipes; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -12,13 +13,13 @@ namespace ObjectPrinting.PrintingConfigs; public class PrintingConfig { private int maxRecursionDepth = 16; - - internal readonly HashSet ExcludedTypes = new(); - internal readonly HashSet ExcludedMembers = new(); - internal readonly Dictionary> TypeSerializers = new(); - internal readonly Dictionary> MemberSerializers = new(); - internal readonly Dictionary TypeCultures = new(); - internal readonly Dictionary TrimLengths = new(); + + private readonly HashSet excludedTypes = new(); + private readonly HashSet excludedMembers = new(); + private readonly Dictionary> typeSerializers = new(); + private readonly Dictionary> memberSerializers = new(); + private readonly Dictionary typeCultures = new(); + private readonly Dictionary trimLengths = new(); public PrintingConfig SetMaxRecursionDepth(int recursionDepth) { @@ -32,13 +33,13 @@ public PrintingConfig SetMaxRecursionDepth(int recursionDepth) public PrintingConfig Excluding() { - ExcludedTypes.Add(typeof(TProp)); + excludedTypes.Add(typeof(TProp)); return this; } public PrintingConfig Excluding(Expression> selector) { - ExcludedMembers.Add(GetMember(selector)); + excludedMembers.Add(GetMember(selector)); return this; } @@ -49,12 +50,43 @@ public TypePrintingConfig Printing() public PropertyPrintingConfig Printing(Expression> selector) { - return new PropertyPrintingConfig(this, GetMember(selector)); + return new PropertyPrintingConfig(this, selector); } public StringPropertyPrintingConfig Printing(Expression> selector) { - return new StringPropertyPrintingConfig(this, GetMember(selector)); + return new StringPropertyPrintingConfig(this, selector); + } + + public PrintingConfig SetSerializerFor(Func serializer) + { + typeSerializers[typeof(TProp)] = p => serializer((TProp)p); + return this; + } + + public PrintingConfig SetSerializerFor( + Expression> selector, Func serializer) + { + memberSerializers[GetMember(selector)] = m => serializer((TProp)m); + return this; + } + + public PrintingConfig SetCultureFor(CultureInfo culture) + where TProp : IFormattable + { + typeCultures[typeof(TProp)] = culture; + return this; + } + + public PrintingConfig TrimMember(Expression> selector, int trimLength) + { + if (trimLength < 0) + { + throw new ArgumentException("Max length cannot be less than zero."); + } + + trimLengths[GetMember(selector)] = trimLength; + return this; } public string PrintToString(TOwner obj) @@ -76,7 +108,7 @@ private string PrintToString(object? obj, int nestingLevel, HashSet visi var type = obj.GetType(); - if (ExcludedTypes.Contains(type)) + if (excludedTypes.Contains(type)) { return string.Empty; } @@ -124,13 +156,13 @@ private string PrintObject(object obj, int nestingLevel, HashSet visited var members = type.GetProperties() .Concat(type.GetFields().Cast()) - .Where(m => !ExcludedMembers.Contains(m)); + .Where(m => !excludedMembers.Contains(m)); foreach (var member in members) { var value = GetMemberValue(obj, member); - if (value is not null && ExcludedTypes.Contains(value.GetType())) + if (value is not null && excludedTypes.Contains(value.GetType())) { continue; } @@ -196,7 +228,7 @@ private string PrintDictionary(IDictionary dict, int nesting, HashSet vi private bool TrySerializeType(Type type, object obj, out string? result) { - if (TypeSerializers.TryGetValue(type, out var serializer)) + if (typeSerializers.TryGetValue(type, out var serializer)) { result = serializer(obj); return true; @@ -208,7 +240,7 @@ private bool TrySerializeType(Type type, object obj, out string? result) private bool TrySerializeMember(MemberInfo member, object? value, out string? result) { - if (MemberSerializers.TryGetValue(member, out var serializer)) + if (memberSerializers.TryGetValue(member, out var serializer)) { result = serializer(value); return true; @@ -220,7 +252,7 @@ private bool TrySerializeMember(MemberInfo member, object? value, out string? re private bool TryFormatFormattable(object obj, Type type, out string result) { - if (obj is IFormattable formattable && TypeCultures.TryGetValue(type, out var culture)) + if (obj is IFormattable formattable && typeCultures.TryGetValue(type, out var culture)) { result = Convert.ToString(formattable, culture) + Environment.NewLine; return true; @@ -232,7 +264,7 @@ private bool TryFormatFormattable(object obj, Type type, out string result) private bool TryTrimStringMember(MemberInfo member, object? value, out string? result) { - if (value is string stringValue && TrimLengths.TryGetValue(member, out int trimLength)) + if (value is string stringValue && trimLengths.TryGetValue(member, out int trimLength)) { result = stringValue.Length > trimLength ? stringValue.Substring(0, trimLength) diff --git a/ObjectPrinting/PrintingConfigs/PropertyPrintingConfig.cs b/ObjectPrinting/PrintingConfigs/PropertyPrintingConfig.cs index 9a4800d62..878710c83 100644 --- a/ObjectPrinting/PrintingConfigs/PropertyPrintingConfig.cs +++ b/ObjectPrinting/PrintingConfigs/PropertyPrintingConfig.cs @@ -1,4 +1,5 @@ using System; +using System.Linq.Expressions; using System.Reflection; namespace ObjectPrinting.PrintingConfigs; @@ -6,17 +7,16 @@ namespace ObjectPrinting.PrintingConfigs; public class PropertyPrintingConfig { protected readonly PrintingConfig Config; - protected readonly MemberInfo Member; + protected readonly Expression> Selector; - public PropertyPrintingConfig(PrintingConfig config, MemberInfo member) + public PropertyPrintingConfig(PrintingConfig config, Expression> selector) { Config = config; - Member = member; + Selector = selector; } public PrintingConfig Using(Func serializer) { - Config.MemberSerializers[Member] = m => serializer((TProp)m); - return Config; + return Config.SetSerializerFor(Selector, serializer); } } \ No newline at end of file diff --git a/ObjectPrinting/PrintingConfigs/StringPropertyPrintingConfig.cs b/ObjectPrinting/PrintingConfigs/StringPropertyPrintingConfig.cs index 21ac53a7c..fc410fd38 100644 --- a/ObjectPrinting/PrintingConfigs/StringPropertyPrintingConfig.cs +++ b/ObjectPrinting/PrintingConfigs/StringPropertyPrintingConfig.cs @@ -1,23 +1,19 @@ using System; +using System.Linq.Expressions; using System.Reflection; namespace ObjectPrinting.PrintingConfigs; public class StringPropertyPrintingConfig : PropertyPrintingConfig { - public StringPropertyPrintingConfig(PrintingConfig config, MemberInfo member) : base(config, member) + public StringPropertyPrintingConfig( + PrintingConfig config, Expression> selector) + : base(config, selector) { } public PrintingConfig TrimToLength(int maxLength) { - if (maxLength < 0) - { - throw new ArgumentException("Max length cannot be less than zero."); - } - - Config.TrimLengths[Member] = maxLength; - - return Config; + return Config.TrimMember(Selector, maxLength); } } \ No newline at end of file diff --git a/ObjectPrinting/PrintingConfigs/TypePrintingConfig.cs b/ObjectPrinting/PrintingConfigs/TypePrintingConfig.cs index 46e64dfd1..2b6fcfbc5 100644 --- a/ObjectPrinting/PrintingConfigs/TypePrintingConfig.cs +++ b/ObjectPrinting/PrintingConfigs/TypePrintingConfig.cs @@ -13,7 +13,6 @@ internal TypePrintingConfig(PrintingConfig config) public PrintingConfig Using(Func serializer) { - Config.TypeSerializers[typeof(TProp)] = p => serializer((TProp)p); - return Config; + return Config.SetSerializerFor(serializer); } } \ No newline at end of file From a9ababefbd9cf0e82cbf9569664d1ecef2d0a03c Mon Sep 17 00:00:00 2001 From: Vladimir Golubev Date: Sun, 23 Nov 2025 13:52:44 +0500 Subject: [PATCH 26/41] feat: add printer settings class --- ObjectPrinting/PrinterSettings.cs | 35 +++++++++++++++++++ .../PrintingConfigs/PrintingConfig.cs | 2 -- 2 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 ObjectPrinting/PrinterSettings.cs diff --git a/ObjectPrinting/PrinterSettings.cs b/ObjectPrinting/PrinterSettings.cs new file mode 100644 index 000000000..7196ead80 --- /dev/null +++ b/ObjectPrinting/PrinterSettings.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Reflection; + +namespace ObjectPrinting; + +public class PrinterSettings +{ + public int MaxRecursionDepth { get; } + public IReadOnlySet ExcludedTypes { get; } + public IReadOnlySet ExcludedMembers { get; } + public IReadOnlyDictionary> TypeSerializers { get; } + public IReadOnlyDictionary> MemberSerializers { get; } + public IReadOnlyDictionary TypeCultures { get; } + public IReadOnlyDictionary TrimLengths { get; } + + public PrinterSettings( + int maxRecursionDepth, + IReadOnlySet excludedTypes, + IReadOnlySet excludedMembers, + IReadOnlyDictionary> typeSerializers, + IReadOnlyDictionary> memberSerializers, + IReadOnlyDictionary typeCultures, + IReadOnlyDictionary trimLengths) + { + MaxRecursionDepth = maxRecursionDepth; + ExcludedTypes = excludedTypes; + ExcludedMembers = excludedMembers; + TypeSerializers = typeSerializers; + MemberSerializers = memberSerializers; + TypeCultures = typeCultures; + TrimLengths = trimLengths; + } +} \ No newline at end of file diff --git a/ObjectPrinting/PrintingConfigs/PrintingConfig.cs b/ObjectPrinting/PrintingConfigs/PrintingConfig.cs index b121a79e5..10b1146b4 100644 --- a/ObjectPrinting/PrintingConfigs/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfigs/PrintingConfig.cs @@ -2,7 +2,6 @@ using System.Collections; using System.Collections.Generic; using System.Globalization; -using System.IO.Pipes; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -13,7 +12,6 @@ namespace ObjectPrinting.PrintingConfigs; public class PrintingConfig { private int maxRecursionDepth = 16; - private readonly HashSet excludedTypes = new(); private readonly HashSet excludedMembers = new(); private readonly Dictionary> typeSerializers = new(); From 42c4d9ab1c1afadc4bd71cf9106c9340c62a6452 Mon Sep 17 00:00:00 2001 From: Vladimir Golubev Date: Sun, 23 Nov 2025 14:17:45 +0500 Subject: [PATCH 27/41] fix: brought out the logic for printing an object in ObjectPrinter class --- ObjectPrinting/ObjectPrinter.cs | 227 +++++++++++++++++- .../Extensions/ObjectExtensions.cs | 4 +- .../PrintingConfigs/PrintingConfig.cs | 216 +---------------- Tests/Tests/ObjectPrinterAcceptanceTests.cs | 3 +- .../ObjectPrintingTests.CollectionTests.cs | 18 +- .../Tests/ObjectPrintingTests.CultureTests.cs | 6 +- ...ectPrintingTests.CustomSerializersTests.cs | 21 +- .../ObjectPrintingTests.ExcludingTests.cs | 18 +- .../ObjectPrintingTests.NestedObjectsTest.cs | 12 +- Tests/Tests/ObjectPrintingTests.TrimTests.cs | 6 +- Tests/Tests/ObjectPrintingTests.cs | 5 +- 11 files changed, 295 insertions(+), 241 deletions(-) diff --git a/ObjectPrinting/ObjectPrinter.cs b/ObjectPrinting/ObjectPrinter.cs index 15b509d84..e65bf2b48 100644 --- a/ObjectPrinting/ObjectPrinter.cs +++ b/ObjectPrinting/ObjectPrinter.cs @@ -1,12 +1,231 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; using ObjectPrinting.PrintingConfigs; -namespace ObjectPrinting +namespace ObjectPrinting; + +public class ObjectPrinter { - public class ObjectPrinter + private readonly PrinterSettings settings; + + public ObjectPrinter(PrinterSettings settings) + { + this.settings = settings; + } + + public static PrintingConfig For() + { + return new PrintingConfig(); + } + + public string PrintToString(TOwner obj) + { + return PrintToString(obj, 0, new HashSet()); + } + + private string PrintToString(object? obj, int nestingLevel, HashSet visited) + { + if (nestingLevel > settings.MaxRecursionDepth) + { + return string.Empty; + } + + if (obj is null) + { + return "null" + Environment.NewLine; + } + + var type = obj.GetType(); + + if (settings.ExcludedTypes.Contains(type)) + { + return string.Empty; + } + + if (!type.IsValueType) + { + if (visited.Contains(obj)) + { + return "(cyclic reference)" + Environment.NewLine; + } + + visited.Add(obj); + } + + return FormatObjectByType(obj, nestingLevel, visited, type); + } + + private string FormatObjectByType(object obj, int nestingLevel, HashSet visited, Type type) + { + if (TrySerializeType(type, obj, out string? result)) + return result + Environment.NewLine; + + if (TryFormatFormattable(obj, type, out result)) + return result; + + if (IsFinalType(type)) + return obj + Environment.NewLine; + + if (obj is IDictionary dictionary) + return PrintDictionary(dictionary, nestingLevel, visited); + + if (obj is IEnumerable enumerable) + return PrintEnumerable(enumerable, nestingLevel, visited); + + return PrintObject(obj, nestingLevel, visited); + } + + private string PrintObject(object obj, int nestingLevel, HashSet visited) + { + var type = obj.GetType(); + var indent = new string('\t', nestingLevel + 1); + var sb = new StringBuilder(); + + sb.AppendLine(type.Name); + + var members = type.GetProperties() + .Concat(type.GetFields().Cast()) + .Where(m => !settings.ExcludedMembers.Contains(m)); + + foreach (var member in members) + { + var value = GetMemberValue(obj, member); + + if (value is not null && settings.ExcludedTypes.Contains(value.GetType())) + { + continue; + } + + sb.Append(indent + member.Name + " = "); + + if (TrySerializeMember(member, value, out var result)) + { + sb.Append(result).AppendLine(); + continue; + } + + if (TryTrimStringMember(member, value, out var trimmed)) + { + sb.Append(trimmed).AppendLine(); + continue; + } + + sb.Append(PrintToString(value, nestingLevel + 1, visited)); + } + + return sb.ToString(); + } + + private string PrintEnumerable(IEnumerable enumerable, int nestingLevel, HashSet visited) + { + var indent = new string('\t', nestingLevel + 1); + var sb = new StringBuilder(); + + sb.AppendLine("["); + + foreach (var item in enumerable) + { + sb.Append(indent) + .Append(PrintToString(item, nestingLevel + 1, visited)); + } + + sb.Append(new string('\t', nestingLevel)).AppendLine("]"); + return sb.ToString(); + } + + private string PrintDictionary(IDictionary dict, int nesting, HashSet visited) + { + var indent = new string('\t', nesting + 1); + var sb = new StringBuilder(); + + sb.AppendLine("{"); + + foreach (DictionaryEntry entry in dict) + { + var key = entry.Key; + var value = entry.Value; + + sb.Append(indent + "["); + sb.Append(PrintToString(key, nesting + 1, visited).TrimEnd()); + sb.Append("] = "); + sb.Append(PrintToString(value, nesting + 1, visited)); + } + + sb.Append(new string('\t', nesting)).AppendLine("}"); + return sb.ToString(); + } + + private bool TrySerializeType(Type type, object obj, out string? result) { - public static PrintingConfig For() + if (settings.TypeSerializers.TryGetValue(type, out var serializer)) { - return new PrintingConfig(); + result = serializer(obj); + return true; } + + result = null; + return false; + } + + private bool TrySerializeMember(MemberInfo member, object? value, out string? result) + { + if (settings.MemberSerializers.TryGetValue(member, out var serializer)) + { + result = serializer(value); + return true; + } + + result = null; + return false; + } + + private bool TryFormatFormattable(object obj, Type type, out string result) + { + if (obj is IFormattable formattable && settings.TypeCultures.TryGetValue(type, out var culture)) + { + result = Convert.ToString(formattable, culture) + Environment.NewLine; + return true; + } + + result = string.Empty; + return false; + } + + private bool TryTrimStringMember(MemberInfo member, object? value, out string? result) + { + if (value is string stringValue && settings.TrimLengths.TryGetValue(member, out int trimLength)) + { + result = stringValue.Length > trimLength + ? stringValue.Substring(0, trimLength) + : stringValue; + return true; + } + + result = null; + return false; + } + + private static bool IsFinalType(Type type) + { + return type.IsPrimitive + || type == typeof(string) + || type == typeof(DateTime) + || type == typeof(TimeSpan) + || type == typeof(decimal) + || type == typeof(Guid); + } + + private static object? GetMemberValue(object? obj, MemberInfo member) + { + return member switch + { + PropertyInfo p => p.GetValue(obj), + FieldInfo f => f.GetValue(obj), + _ => throw new InvalidOperationException($"Unsupported member type: {member.MemberType}") + }; } } \ No newline at end of file diff --git a/ObjectPrinting/PrintingConfigs/Extensions/ObjectExtensions.cs b/ObjectPrinting/PrintingConfigs/Extensions/ObjectExtensions.cs index b0c8f2f8b..169f085e8 100644 --- a/ObjectPrinting/PrintingConfigs/Extensions/ObjectExtensions.cs +++ b/ObjectPrinting/PrintingConfigs/Extensions/ObjectExtensions.cs @@ -6,12 +6,12 @@ public static class ObjectExtensions { public static string PrintToString(this T obj) { - return ObjectPrinter.For().PrintToString(obj); + return ObjectPrinter.For().CreatePrinter().PrintToString(obj); } public static string PrintToString(this T obj, Func, PrintingConfig> config) { - return config(ObjectPrinter.For()).PrintToString(obj); + return config(ObjectPrinter.For()).CreatePrinter().PrintToString(obj); } } diff --git a/ObjectPrinting/PrintingConfigs/PrintingConfig.cs b/ObjectPrinting/PrintingConfigs/PrintingConfig.cs index 10b1146b4..f30cf768f 100644 --- a/ObjectPrinting/PrintingConfigs/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfigs/PrintingConfig.cs @@ -1,11 +1,8 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Globalization; -using System.Linq; using System.Linq.Expressions; using System.Reflection; -using System.Text; namespace ObjectPrinting.PrintingConfigs; @@ -86,202 +83,23 @@ public PrintingConfig TrimMember(Expression> select trimLengths[GetMember(selector)] = trimLength; return this; } - - public string PrintToString(TOwner obj) - { - return PrintToString(obj, 0, new HashSet()); - } - - private string PrintToString(object? obj, int nestingLevel, HashSet visited) - { - if (nestingLevel > maxRecursionDepth) - { - return string.Empty; - } - - if (obj is null) - { - return "null" + Environment.NewLine; - } - - var type = obj.GetType(); - - if (excludedTypes.Contains(type)) - { - return string.Empty; - } - - if (!type.IsValueType) - { - if (visited.Contains(obj)) - { - return "(cyclic reference)" + Environment.NewLine; - } - - visited.Add(obj); - } - - return FormatObjectByType(obj, nestingLevel, visited, type); - } - - private string FormatObjectByType(object obj, int nestingLevel, HashSet visited, Type type) - { - if (TrySerializeType(type, obj, out string? result)) - return result + Environment.NewLine; - - if (TryFormatFormattable(obj, type, out result)) - return result; - - if (IsFinalType(type)) - return obj + Environment.NewLine; - - if (obj is IDictionary dictionary) - return PrintDictionary(dictionary, nestingLevel, visited); - - if (obj is IEnumerable enumerable) - return PrintEnumerable(enumerable, nestingLevel, visited); - - return PrintObject(obj, nestingLevel, visited); - } - - private string PrintObject(object obj, int nestingLevel, HashSet visited) - { - var type = obj.GetType(); - var indent = new string('\t', nestingLevel + 1); - var sb = new StringBuilder(); - - sb.AppendLine(type.Name); - - var members = type.GetProperties() - .Concat(type.GetFields().Cast()) - .Where(m => !excludedMembers.Contains(m)); - - foreach (var member in members) - { - var value = GetMemberValue(obj, member); - - if (value is not null && excludedTypes.Contains(value.GetType())) - { - continue; - } - - sb.Append(indent + member.Name + " = "); - - if (TrySerializeMember(member, value, out var result)) - { - sb.Append(result).AppendLine(); - continue; - } - - if (TryTrimStringMember(member, value, out var trimmed)) - { - sb.Append(trimmed).AppendLine(); - continue; - } - - sb.Append(PrintToString(value, nestingLevel + 1, visited)); - } - - return sb.ToString(); - } - private string PrintEnumerable(IEnumerable enumerable, int nestingLevel, HashSet visited) + public PrinterSettings CreateSettings() { - var indent = new string('\t', nestingLevel + 1); - var sb = new StringBuilder(); - - sb.AppendLine("["); - - foreach (var item in enumerable) - { - sb.Append(indent) - .Append(PrintToString(item, nestingLevel + 1, visited)); - } - - sb.Append(new string('\t', nestingLevel)).AppendLine("]"); - return sb.ToString(); - } - - private string PrintDictionary(IDictionary dict, int nesting, HashSet visited) - { - var indent = new string('\t', nesting + 1); - var sb = new StringBuilder(); - - sb.AppendLine("{"); - - foreach (DictionaryEntry entry in dict) - { - var key = entry.Key; - var value = entry.Value; - - sb.Append(indent + "["); - sb.Append(PrintToString(key, nesting + 1, visited).TrimEnd()); - sb.Append("] = "); - sb.Append(PrintToString(value, nesting + 1, visited)); - } - - sb.Append(new string('\t', nesting)).AppendLine("}"); - return sb.ToString(); - } - - private bool TrySerializeType(Type type, object obj, out string? result) - { - if (typeSerializers.TryGetValue(type, out var serializer)) - { - result = serializer(obj); - return true; - } - - result = null; - return false; - } - - private bool TrySerializeMember(MemberInfo member, object? value, out string? result) - { - if (memberSerializers.TryGetValue(member, out var serializer)) - { - result = serializer(value); - return true; - } - - result = null; - return false; + return new PrinterSettings( + maxRecursionDepth, + new HashSet(excludedTypes), + new HashSet(excludedMembers), + new Dictionary>(typeSerializers), + new Dictionary>(memberSerializers), + new Dictionary(typeCultures), + new Dictionary(trimLengths) + ); } - private bool TryFormatFormattable(object obj, Type type, out string result) + public ObjectPrinter CreatePrinter() { - if (obj is IFormattable formattable && typeCultures.TryGetValue(type, out var culture)) - { - result = Convert.ToString(formattable, culture) + Environment.NewLine; - return true; - } - - result = string.Empty; - return false; - } - - private bool TryTrimStringMember(MemberInfo member, object? value, out string? result) - { - if (value is string stringValue && trimLengths.TryGetValue(member, out int trimLength)) - { - result = stringValue.Length > trimLength - ? stringValue.Substring(0, trimLength) - : stringValue; - return true; - } - - result = null; - return false; - } - - private static bool IsFinalType(Type type) - { - return type.IsPrimitive - || type == typeof(string) - || type == typeof(DateTime) - || type == typeof(TimeSpan) - || type == typeof(decimal) - || type == typeof(Guid); + return new ObjectPrinter(CreateSettings()); } private static MemberInfo GetMember(Expression> selector) @@ -293,14 +111,4 @@ private static MemberInfo GetMember(Expression> selec throw new ArgumentException("Selector must refer to a property or a field."); } - - private static object? GetMemberValue(object? obj, MemberInfo member) - { - return member switch - { - PropertyInfo p => p.GetValue(obj), - FieldInfo f => f.GetValue(obj), - _ => throw new InvalidOperationException($"Unsupported member type: {member.MemberType}") - }; - } } \ No newline at end of file diff --git a/Tests/Tests/ObjectPrinterAcceptanceTests.cs b/Tests/Tests/ObjectPrinterAcceptanceTests.cs index b8b7f48c0..307c897b5 100644 --- a/Tests/Tests/ObjectPrinterAcceptanceTests.cs +++ b/Tests/Tests/ObjectPrinterAcceptanceTests.cs @@ -20,7 +20,8 @@ public void Demo() .Printing().Using(CultureInfo.InvariantCulture) // 3. Для числовых типов указать культуру .Printing(p => p.Age).Using(a => $"AGE : {a}") // 4. Настроить сериализацию конкретного свойства .Printing(p => p.Name).TrimToLength(10) // 5. Настроить обрезание строковых свойств - .Excluding(p => p.Age); // 6. Исключить конкретное свойство + .Excluding(p => p.Age) + .CreatePrinter(); // 6. Исключить конкретное свойство var s1 = printer.PrintToString(person); diff --git a/Tests/Tests/ObjectPrintingTests.CollectionTests.cs b/Tests/Tests/ObjectPrintingTests.CollectionTests.cs index fb9da9cec..ec5050623 100644 --- a/Tests/Tests/ObjectPrintingTests.CollectionTests.cs +++ b/Tests/Tests/ObjectPrintingTests.CollectionTests.cs @@ -17,7 +17,7 @@ private class Department public void PrintToString_Array_ShouldPrintEachElement() { var data = new[] { 1, 2, 3 }; - var printer = ObjectPrinter.For(); + var printer = ObjectPrinter.For().CreatePrinter(); var result = printer.PrintToString(data); @@ -37,7 +37,7 @@ public void PrintToString_ArrayOfObjects_ShouldSerializeEachElement() new CollectionTestsClass { Name = "Alice", Age = 25 }, new CollectionTestsClass { Name = "Bob", Age = 30 } }; - var printer = ObjectPrinter.For(); + var printer = ObjectPrinter.For().CreatePrinter(); var result = printer.PrintToString(data); @@ -55,7 +55,7 @@ public void PrintToString_Dictionary_ShoulPrintKeysAndValues() [2] = "2" }; - var printer = ObjectPrinter.For>(); + var printer = ObjectPrinter.For>().CreatePrinter(); var result = printer.PrintToString(dict); result.Should() @@ -73,7 +73,9 @@ public void PrintToString_DictionaryWithComplexObjects_ShouldSerializeKeysAndVal [new CollectionTestsClass { Name = "Developer" }] = new Department { Name = "IT" } }; - var printer = ObjectPrinter.For>(); + var printer = ObjectPrinter + .For>() + .CreatePrinter(); var result = printer.PrintToString(dict); result.Should().MatchRegex( @@ -85,7 +87,9 @@ public void PrintToString_DictionaryWithComplexObjects_ShouldSerializeKeysAndVal public void PrintToString_EmptyCollection_ShouldShowEmptyStructure() { var data = new CollectionTestsClass { Name = "Test", Friends = new List() }; - var printer = ObjectPrinter.For(); + var printer = ObjectPrinter + .For() + .CreatePrinter(); var result = printer.PrintToString(data); result.Should().Contain("Friends = ["); @@ -95,7 +99,9 @@ public void PrintToString_EmptyCollection_ShouldShowEmptyStructure() public void PrintToString_NullCollection_ShouldShowNull() { var data = new CollectionTestsClass() { Name = "Test", Friends = null }; - var printer = ObjectPrinter.For(); + var printer = ObjectPrinter + .For() + .CreatePrinter(); var result = printer.PrintToString(data); result.Should().Contain("Friends = null"); diff --git a/Tests/Tests/ObjectPrintingTests.CultureTests.cs b/Tests/Tests/ObjectPrintingTests.CultureTests.cs index 561c350a6..34b3618a2 100644 --- a/Tests/Tests/ObjectPrintingTests.CultureTests.cs +++ b/Tests/Tests/ObjectPrintingTests.CultureTests.cs @@ -34,7 +34,8 @@ public void SetUp() public void PrintToString_CustomCulture_ShouldApplyFormatting() { var printer = ObjectPrinter.For() - .Printing().Using(new CultureInfo("de-DE")); + .Printing().Using(new CultureInfo("de-DE")) + .CreatePrinter(); var result = printer.PrintToString(testData); @@ -47,7 +48,8 @@ public void PrintToString_MultipleCultures_ShouldApplyAll() var printer = ObjectPrinter.For() .Printing().Using(new CultureInfo("es-ES")) .Printing().Using(new CultureInfo("en-US")) - .Printing().Using(new CultureInfo("fr-FR")); + .Printing().Using(new CultureInfo("fr-FR")) + .CreatePrinter(); var result = printer.PrintToString(testData); diff --git a/Tests/Tests/ObjectPrintingTests.CustomSerializersTests.cs b/Tests/Tests/ObjectPrintingTests.CustomSerializersTests.cs index d0539317e..903e3c671 100644 --- a/Tests/Tests/ObjectPrintingTests.CustomSerializersTests.cs +++ b/Tests/Tests/ObjectPrintingTests.CustomSerializersTests.cs @@ -39,7 +39,8 @@ public void PrintToString_CustomTypeSerializer_ShouldFormatType() { var printer = ObjectPrinter.For() .Printing().Using(g => g - .ToString("N").Substring(0, 8).ToUpper()); + .ToString("N").Substring(0, 8).ToUpper()) + .CreatePrinter(); var result = printer.PrintToString(testData); @@ -51,7 +52,8 @@ public void PrintToString_CustomPropertySerializer_ShouldOverrideTypeSerializer( { var printer = ObjectPrinter.For() .Printing().Using(i => $"{i} years") - .Printing(p => p.Age).Using(age => $"Age: {age}"); + .Printing(p => p.Age).Using(age => $"Age: {age}") + .CreatePrinter(); var result = printer.PrintToString(testData); @@ -63,7 +65,8 @@ public void PrintToString_CustomPropertySerializer_ShouldOverrideTypeSerializer( public void PrintToString_CustomSerializerReturnsNull_ShouldHandleCorrectly() { var printer = ObjectPrinter.For() - .Printing(p => p.Name).Using(_ => null); + .Printing(p => p.Name).Using(_ => null) + .CreatePrinter(); var result = printer.PrintToString(testData); @@ -76,7 +79,8 @@ public void PrintToString_CustomSerializerForMultipleTypes_ShouldApplyAll() var printer = ObjectPrinter.For() .Printing().Using(g => g.ToString().Substring(0, 8)) .Printing().Using(i => $"#{i}") - .Printing().Using(p => $"${p}"); + .Printing().Using(p => $"${p}") + .CreatePrinter(); var result = printer.PrintToString(testData); @@ -90,7 +94,8 @@ public void PrintToString_CustomSerializerForMultipleTypes_ShouldApplyAll() public void PrintToString_CustomSerializerForOneProperty_ShouldNotAffectOtherProperties() { var printer = ObjectPrinter.For() - .Printing(p => p.Price).Using(p => $"${p}"); + .Printing(p => p.Price).Using(p => $"${p}") + .CreatePrinter(); var result = printer.PrintToString(testData); @@ -105,7 +110,8 @@ public void PrintToString_MultiplePropertySerializers_ShouldApplyEachToRespectiv var printer = ObjectPrinter.For() .Printing(p => p.Name).Using(n => $"{n.ToUpper()}") .Printing(p => p.Age).Using(a => $"{a} years") - .Printing(p => p.Price).Using(p => $"${p}"); + .Printing(p => p.Price).Using(p => $"${p}") + .CreatePrinter(); var result = printer.PrintToString(testData); @@ -120,7 +126,8 @@ public void PrintToString_CustomSerializerDominatesOverCulture() var data = new { Price = 1234.56d, Weight = 7.89d }; var printer = ObjectPrinter.For() .Printing().Using(new CultureInfo("de-DE")) - .Printing().Using(p => $"${p:0.00}"); + .Printing().Using(p => $"${p:0.00}") + .CreatePrinter(); var result = printer.PrintToString(data); diff --git a/Tests/Tests/ObjectPrintingTests.ExcludingTests.cs b/Tests/Tests/ObjectPrintingTests.ExcludingTests.cs index e5b7ec76b..9de8be416 100644 --- a/Tests/Tests/ObjectPrintingTests.ExcludingTests.cs +++ b/Tests/Tests/ObjectPrintingTests.ExcludingTests.cs @@ -24,7 +24,8 @@ public void SetUp() public void PrintToString_ExcludedType_ShouldNotSerializeMembersOfThatType() { var printer = new PrintingConfig() - .Excluding(); + .Excluding() + .CreatePrinter(); var result = printer.PrintToString(person); @@ -37,7 +38,8 @@ public void PrintToString_ExcludedType_ShouldNotSerializeAllMembersOfThatType() var obj = new TestClass { Property = "Prop", Field = "Field" }; var printer = ObjectPrinter.For() - .Excluding(); + .Excluding() + .CreatePrinter(); var result = printer.PrintToString(obj); result.Should().NotContain("Prop").And.NotContain("Field"); @@ -47,7 +49,8 @@ public void PrintToString_ExcludedType_ShouldNotSerializeAllMembersOfThatType() public void PrintToString_ExcludedProperty_ShouldNotSerializeProperty() { var printer = new PrintingConfig() - .Excluding(p => p.Age); + .Excluding(p => p.Age) + .CreatePrinter(); var result = printer.PrintToString(person); @@ -69,9 +72,9 @@ public void PrintToString_ExcludedMultipleMembers_ShouldNotSerializeMultipleMemb { var printer = ObjectPrinter.For() .Excluding(p => p.Id) - .Excluding(p => p.Age); - - + .Excluding(p => p.Age) + .CreatePrinter(); + var result = printer.PrintToString(person); result.Should().NotContain("Id").And.NotContain("Age"); } @@ -93,7 +96,8 @@ public void PrintToString_AllPropertiesExcluded_ShouldShowTypeName() .Excluding() .Excluding() .Excluding(p => p.Id) - .Excluding(p => p.Height); + .Excluding(p => p.Height) + .CreatePrinter(); var result = printer.PrintToString(person); diff --git a/Tests/Tests/ObjectPrintingTests.NestedObjectsTest.cs b/Tests/Tests/ObjectPrintingTests.NestedObjectsTest.cs index fb6f912ca..747f89200 100644 --- a/Tests/Tests/ObjectPrintingTests.NestedObjectsTest.cs +++ b/Tests/Tests/ObjectPrintingTests.NestedObjectsTest.cs @@ -13,7 +13,9 @@ public class NestedObjectsTest public void PrintToString_NestedObject_ShouldPrintAllLevels() { var node = Node.CreateChain(3); - var printer = ObjectPrinter.For(); + var printer = ObjectPrinter + .For() + .CreatePrinter(); var result = printer.PrintToString(node); @@ -26,7 +28,9 @@ public void PrintToString_NestedObject_ShouldPrintAllLevels() public void PrintToString_WithCyclicReference_ShouldNotFall() { var node = Node.CreateChainWithCycle(); - var printer = ObjectPrinter.For(); + var printer = ObjectPrinter + .For() + .CreatePrinter(); var act = () => printer.PrintToString(node); @@ -38,7 +42,9 @@ public void PrintToString_WithCyclicReference_ShouldNotFall() public void PrintToString_WhenReachesMaxRecursionLevel_ShouldStop() { var node = Node.CreateChain(16); - var printer = ObjectPrinter.For(); + var printer = ObjectPrinter + .For() + .CreatePrinter(); var result = printer.PrintToString(node); diff --git a/Tests/Tests/ObjectPrintingTests.TrimTests.cs b/Tests/Tests/ObjectPrintingTests.TrimTests.cs index f97a62ff7..62b97e9f2 100644 --- a/Tests/Tests/ObjectPrintingTests.TrimTests.cs +++ b/Tests/Tests/ObjectPrintingTests.TrimTests.cs @@ -20,7 +20,8 @@ public void PrintToString_TrimToZeroLength_ShouldReturnEmptyString() { var data = new TrimTestsClass { Long = "Very long text" }; var printer = ObjectPrinter.For() - .Printing(p => p.Long).TrimToLength(0); + .Printing(p => p.Long).TrimToLength(0) + .CreatePrinter(); var result = printer.PrintToString(data); @@ -40,7 +41,8 @@ public void PrintToString_TrimMultipleStringProperties_ShouldTrimEachIndependent var printer = ObjectPrinter.For() .Printing(p => p.Short).TrimToLength(3) .Printing(p => p.Long).TrimToLength(10) - .Printing(p => p.Description).TrimToLength(6); + .Printing(p => p.Description).TrimToLength(6) + .CreatePrinter(); var result = printer.PrintToString(data); diff --git a/Tests/Tests/ObjectPrintingTests.cs b/Tests/Tests/ObjectPrintingTests.cs index 024c363d5..89b7d4c11 100644 --- a/Tests/Tests/ObjectPrintingTests.cs +++ b/Tests/Tests/ObjectPrintingTests.cs @@ -65,12 +65,11 @@ public async Task PrintToString_WithMaximumConfigurations_ShouldApplyAllCorrectl .Printing().Using(new CultureInfo("de-DE")) .Printing().Using(new CultureInfo("fr-FR")) .Printing>().Using(list => $"Tags[{list.Count}]") - .Printing>().Using(dict => $"Scores[{dict.Count}]"); + .Printing>().Using(dict => $"Scores[{dict.Count}]") + .CreatePrinter(); var result = printer.PrintToString(testData); await Verify(result); } - - } \ No newline at end of file From 784cf126813b8bc78bb961615cf064b1ec8bf48a Mon Sep 17 00:00:00 2001 From: Vladimir Golubev Date: Sun, 23 Nov 2025 14:19:46 +0500 Subject: [PATCH 28/41] fix: rename PrintToString method --- ObjectPrinting/PrintingConfigs/Extensions/ObjectExtensions.cs | 4 ++-- Tests/Tests/ObjectPrinterAcceptanceTests.cs | 4 ++-- Tests/Tests/ObjectPrintingTests.ExcludingTests.cs | 2 +- Tests/Tests/ObjectPrintingTests.cs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ObjectPrinting/PrintingConfigs/Extensions/ObjectExtensions.cs b/ObjectPrinting/PrintingConfigs/Extensions/ObjectExtensions.cs index 169f085e8..42ad97135 100644 --- a/ObjectPrinting/PrintingConfigs/Extensions/ObjectExtensions.cs +++ b/ObjectPrinting/PrintingConfigs/Extensions/ObjectExtensions.cs @@ -4,12 +4,12 @@ namespace ObjectPrinting.PrintingConfigs.Extensions; public static class ObjectExtensions { - public static string PrintToString(this T obj) + public static string Print(this T obj) { return ObjectPrinter.For().CreatePrinter().PrintToString(obj); } - public static string PrintToString(this T obj, Func, PrintingConfig> config) + public static string Print(this T obj, Func, PrintingConfig> config) { return config(ObjectPrinter.For()).CreatePrinter().PrintToString(obj); } diff --git a/Tests/Tests/ObjectPrinterAcceptanceTests.cs b/Tests/Tests/ObjectPrinterAcceptanceTests.cs index 307c897b5..b5f865a72 100644 --- a/Tests/Tests/ObjectPrinterAcceptanceTests.cs +++ b/Tests/Tests/ObjectPrinterAcceptanceTests.cs @@ -25,9 +25,9 @@ public void Demo() var s1 = printer.PrintToString(person); - var s2 = person.PrintToString(); // 7. Синтаксический сахар — метод расширения + var s2 = person.Print(); // 7. Синтаксический сахар — метод расширения - var s3 = person.PrintToString(p => p + var s3 = person.Print(p => p .Printing().Using(CultureInfo.GetCultureInfo("ru-RU")) // 8. ...с конфигурированием ); } diff --git a/Tests/Tests/ObjectPrintingTests.ExcludingTests.cs b/Tests/Tests/ObjectPrintingTests.ExcludingTests.cs index 9de8be416..28a066488 100644 --- a/Tests/Tests/ObjectPrintingTests.ExcludingTests.cs +++ b/Tests/Tests/ObjectPrintingTests.ExcludingTests.cs @@ -62,7 +62,7 @@ public void PrintToString_ExcludedField_ShouldNotSerializeField() { var obj = new TestClass { Property = "Prop", Field = "Field" }; - var result = obj.PrintToString(c => c.Excluding(o => o.Field)); + var result = obj.Print(c => c.Excluding(o => o.Field)); result.Should().Contain("Property").And.NotContain("Field"); } diff --git a/Tests/Tests/ObjectPrintingTests.cs b/Tests/Tests/ObjectPrintingTests.cs index 89b7d4c11..c3c69d3f4 100644 --- a/Tests/Tests/ObjectPrintingTests.cs +++ b/Tests/Tests/ObjectPrintingTests.cs @@ -11,7 +11,7 @@ public partial class ObjectPrintingTests public async Task PrintToString_SimpleObject_ShouldSerializeAllMembers() { var person = new Person { Name = "John", Age = 25 }; - var result = person.PrintToString(); + var result = person.Print(); await Verify(result); } From 8de41a70d58a7a67d23b2d68487a82e4964230fc Mon Sep 17 00:00:00 2001 From: Vladimir Golubev Date: Sun, 23 Nov 2025 14:24:26 +0500 Subject: [PATCH 29/41] fix: remove unnecessary brackets, simplified checking for circular references --- ObjectPrinting/ObjectPrinter.cs | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/ObjectPrinting/ObjectPrinter.cs b/ObjectPrinting/ObjectPrinter.cs index e65bf2b48..d6982e322 100644 --- a/ObjectPrinting/ObjectPrinter.cs +++ b/ObjectPrinting/ObjectPrinter.cs @@ -30,31 +30,18 @@ public string PrintToString(TOwner obj) private string PrintToString(object? obj, int nestingLevel, HashSet visited) { if (nestingLevel > settings.MaxRecursionDepth) - { return string.Empty; - } if (obj is null) - { return "null" + Environment.NewLine; - } var type = obj.GetType(); if (settings.ExcludedTypes.Contains(type)) - { return string.Empty; - } - - if (!type.IsValueType) - { - if (visited.Contains(obj)) - { - return "(cyclic reference)" + Environment.NewLine; - } - visited.Add(obj); - } + if (IsCyclicReference(obj, type, visited)) + return "(cyclic reference)" + Environment.NewLine; return FormatObjectByType(obj, nestingLevel, visited, type); } @@ -209,6 +196,11 @@ private bool TryTrimStringMember(MemberInfo member, object? value, out string? r return false; } + private static bool IsCyclicReference(object obj, Type type, HashSet visited) + { + return !type.IsValueType && !visited.Add(obj); + } + private static bool IsFinalType(Type type) { return type.IsPrimitive From 2b3f553b964d11c102038ebc22c3e8156edfdb3a Mon Sep 17 00:00:00 2001 From: Vladimir Golubev Date: Sun, 23 Nov 2025 15:38:18 +0500 Subject: [PATCH 30/41] fix: refactor ObjectPrinter class --- ObjectPrinting/ObjectPrinter.cs | 135 ++++++++++---------- Tests/Tests/ObjectPrinterAcceptanceTests.cs | 4 +- 2 files changed, 71 insertions(+), 68 deletions(-) diff --git a/ObjectPrinting/ObjectPrinter.cs b/ObjectPrinting/ObjectPrinter.cs index d6982e322..d9f0a73c8 100644 --- a/ObjectPrinting/ObjectPrinter.cs +++ b/ObjectPrinting/ObjectPrinter.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Reflection; using System.Text; @@ -33,7 +34,7 @@ private string PrintToString(object? obj, int nestingLevel, HashSet visi return string.Empty; if (obj is null) - return "null" + Environment.NewLine; + return "null" + NewLine; var type = obj.GetType(); @@ -41,108 +42,102 @@ private string PrintToString(object? obj, int nestingLevel, HashSet visi return string.Empty; if (IsCyclicReference(obj, type, visited)) - return "(cyclic reference)" + Environment.NewLine; + return "(cyclic reference)" + NewLine; - return FormatObjectByType(obj, nestingLevel, visited, type); + return FormatByType(obj, nestingLevel, visited, type); } - private string FormatObjectByType(object obj, int nestingLevel, HashSet visited, Type type) + private string FormatByType(object obj, int nestingLevel, HashSet visited, Type type) { - if (TrySerializeType(type, obj, out string? result)) - return result + Environment.NewLine; + if (TrySerializeType(type, obj, out var result)) + return result + NewLine; if (TryFormatFormattable(obj, type, out result)) - return result; + return result + NewLine; if (IsFinalType(type)) - return obj + Environment.NewLine; - - if (obj is IDictionary dictionary) - return PrintDictionary(dictionary, nestingLevel, visited); - - if (obj is IEnumerable enumerable) - return PrintEnumerable(enumerable, nestingLevel, visited); - - return PrintObject(obj, nestingLevel, visited); + return Convert.ToString(obj, CultureInfo.InvariantCulture) + NewLine; + + return obj switch + { + IDictionary dictionary => PrintDictionary(dictionary, nestingLevel, visited), + IEnumerable enumerable => PrintEnumerable(enumerable, nestingLevel, visited), + _ => PrintObject(obj, nestingLevel, visited) + }; } private string PrintObject(object obj, int nestingLevel, HashSet visited) { var type = obj.GetType(); - var indent = new string('\t', nestingLevel + 1); + var indent = Indent(nestingLevel + 1); var sb = new StringBuilder(); sb.AppendLine(type.Name); - var members = type.GetProperties() - .Concat(type.GetFields().Cast()) - .Where(m => !settings.ExcludedMembers.Contains(m)); + var members = GetFilteredMembers(type); foreach (var member in members) { - var value = GetMemberValue(obj, member); - - if (value is not null && settings.ExcludedTypes.Contains(value.GetType())) - { - continue; - } - - sb.Append(indent + member.Name + " = "); + sb.Append(ProcessMember(obj, member, indent, nestingLevel, visited)); + } - if (TrySerializeMember(member, value, out var result)) - { - sb.Append(result).AppendLine(); - continue; - } + return sb.ToString(); + } + + private string ProcessMember( + object obj, MemberInfo member, string indent, int nestingLevel, HashSet visited) + { + var value = GetMemberValue(obj, member); + var prefix = $"{indent}{member.Name} = "; - if (TryTrimStringMember(member, value, out var trimmed)) - { - sb.Append(trimmed).AppendLine(); - continue; - } + if (value is null) + return prefix + null + NewLine; + + if (settings.ExcludedTypes.Contains(value.GetType())) + return string.Empty; - sb.Append(PrintToString(value, nestingLevel + 1, visited)); - } + if (TrySerializeMember(member, value, out var result)) + return prefix + result + NewLine; + + if (TryTrimStringMember(member, value, out var trimmed)) + return prefix + trimmed + NewLine; - return sb.ToString(); + return prefix + PrintToString(value, nestingLevel + 1, visited); } private string PrintEnumerable(IEnumerable enumerable, int nestingLevel, HashSet visited) { - var indent = new string('\t', nestingLevel + 1); + var indent = Indent(nestingLevel + 1); var sb = new StringBuilder(); sb.AppendLine("["); - + foreach (var item in enumerable) { - sb.Append(indent) - .Append(PrintToString(item, nestingLevel + 1, visited)); + sb.Append(indent).Append(PrintToString(item, nestingLevel + 1, visited)); } - sb.Append(new string('\t', nestingLevel)).AppendLine("]"); + sb.Append(Indent(nestingLevel)).AppendLine("]"); return sb.ToString(); } - private string PrintDictionary(IDictionary dict, int nesting, HashSet visited) + private string PrintDictionary(IDictionary dict, int nestingLevel, HashSet visited) { - var indent = new string('\t', nesting + 1); + var indent = Indent(nestingLevel + 1); var sb = new StringBuilder(); sb.AppendLine("{"); foreach (DictionaryEntry entry in dict) { - var key = entry.Key; - var value = entry.Value; - - sb.Append(indent + "["); - sb.Append(PrintToString(key, nesting + 1, visited).TrimEnd()); - sb.Append("] = "); - sb.Append(PrintToString(value, nesting + 1, visited)); + sb.Append(indent) + .Append('[') + .Append(PrintToString(entry.Key, nestingLevel + 1, visited).TrimEnd()) + .Append("] = ") + .Append(PrintToString(entry.Value, nestingLevel + 1, visited)); } - sb.Append(new string('\t', nesting)).AppendLine("}"); + sb.Append(Indent(nestingLevel)).AppendLine("}"); return sb.ToString(); } @@ -158,7 +153,7 @@ private bool TrySerializeType(Type type, object obj, out string? result) return false; } - private bool TrySerializeMember(MemberInfo member, object? value, out string? result) + private bool TrySerializeMember(MemberInfo member, object value, out string? result) { if (settings.MemberSerializers.TryGetValue(member, out var serializer)) { @@ -170,11 +165,11 @@ private bool TrySerializeMember(MemberInfo member, object? value, out string? re return false; } - private bool TryFormatFormattable(object obj, Type type, out string result) + private bool TryFormatFormattable(object obj, Type type, out string? result) { if (obj is IFormattable formattable && settings.TypeCultures.TryGetValue(type, out var culture)) { - result = Convert.ToString(formattable, culture) + Environment.NewLine; + result = Convert.ToString(formattable, culture); return true; } @@ -182,9 +177,9 @@ private bool TryFormatFormattable(object obj, Type type, out string result) return false; } - private bool TryTrimStringMember(MemberInfo member, object? value, out string? result) + private bool TryTrimStringMember(MemberInfo member, object value, out string? result) { - if (value is string stringValue && settings.TrimLengths.TryGetValue(member, out int trimLength)) + if (value is string stringValue && settings.TrimLengths.TryGetValue(member, out var trimLength)) { result = stringValue.Length > trimLength ? stringValue.Substring(0, trimLength) @@ -205,10 +200,15 @@ private static bool IsFinalType(Type type) { return type.IsPrimitive || type == typeof(string) - || type == typeof(DateTime) - || type == typeof(TimeSpan) - || type == typeof(decimal) - || type == typeof(Guid); + || type.IsEnum + || typeof(IFormattable).IsAssignableFrom(type); + } + + private IEnumerable GetFilteredMembers(Type type) + { + return type.GetProperties() + .Concat(type.GetFields().Cast()) + .Where(m => !settings.ExcludedMembers.Contains(m)); } private static object? GetMemberValue(object? obj, MemberInfo member) @@ -217,7 +217,10 @@ private static bool IsFinalType(Type type) { PropertyInfo p => p.GetValue(obj), FieldInfo f => f.GetValue(obj), - _ => throw new InvalidOperationException($"Unsupported member type: {member.MemberType}") + _ => throw new ArgumentOutOfRangeException($"Unsupported member type: {member.MemberType}") }; } + + private static string Indent(int level) => new('\t', level); + private static string NewLine => Environment.NewLine; } \ No newline at end of file diff --git a/Tests/Tests/ObjectPrinterAcceptanceTests.cs b/Tests/Tests/ObjectPrinterAcceptanceTests.cs index b5f865a72..b8cb9f3d4 100644 --- a/Tests/Tests/ObjectPrinterAcceptanceTests.cs +++ b/Tests/Tests/ObjectPrinterAcceptanceTests.cs @@ -20,8 +20,8 @@ public void Demo() .Printing().Using(CultureInfo.InvariantCulture) // 3. Для числовых типов указать культуру .Printing(p => p.Age).Using(a => $"AGE : {a}") // 4. Настроить сериализацию конкретного свойства .Printing(p => p.Name).TrimToLength(10) // 5. Настроить обрезание строковых свойств - .Excluding(p => p.Age) - .CreatePrinter(); // 6. Исключить конкретное свойство + .Excluding(p => p.Age) // 6. Исключить конкретное свойство + .CreatePrinter(); var s1 = printer.PrintToString(person); From fd55ee0da8e1c33471ba66ca352537698e075eb2 Mon Sep 17 00:00:00 2001 From: Vladimir Golubev Date: Sun, 23 Nov 2025 15:51:48 +0500 Subject: [PATCH 31/41] fix: move test classes to separate folder --- ObjectPrinting/ObjectPrinter.cs | 2 +- Tests/TestEntities/CollectionTestsClass.cs | 8 ++++++++ Tests/TestEntities/CultureTestClass.cs | 8 ++++++++ Tests/TestEntities/CustomSerializerTestsClass.cs | 10 ++++++++++ Tests/TestEntities/Departmnet.cs | 6 ++++++ Tests/TestEntities/Node.cs | 2 +- Tests/TestEntities/Person.cs | 2 +- Tests/TestEntities/SuperComplexClass.cs | 4 ++-- Tests/TestEntities/TestClass.cs | 4 ++-- Tests/TestEntities/TrimTestsClass.cs | 8 ++++++++ Tests/Tests/ObjectPrinterAcceptanceTests.cs | 1 - ...ngTests.CollectionTests.CollectionTestsClass.cs | 14 -------------- Tests/Tests/ObjectPrintingTests.CollectionTests.cs | 8 ++------ Tests/Tests/ObjectPrintingTests.CultureTests.cs | 8 +------- .../ObjectPrintingTests.CustomSerializersTests.cs | 12 ++---------- Tests/Tests/ObjectPrintingTests.ExcludingTests.cs | 6 +++--- Tests/Tests/ObjectPrintingTests.TrimTests.cs | 8 +------- 17 files changed, 56 insertions(+), 55 deletions(-) create mode 100644 Tests/TestEntities/CollectionTestsClass.cs create mode 100644 Tests/TestEntities/CultureTestClass.cs create mode 100644 Tests/TestEntities/CustomSerializerTestsClass.cs create mode 100644 Tests/TestEntities/Departmnet.cs create mode 100644 Tests/TestEntities/TrimTestsClass.cs delete mode 100644 Tests/Tests/ObjectPrintingTests.CollectionTests.CollectionTestsClass.cs diff --git a/ObjectPrinting/ObjectPrinter.cs b/ObjectPrinting/ObjectPrinter.cs index d9f0a73c8..372daa906 100644 --- a/ObjectPrinting/ObjectPrinter.cs +++ b/ObjectPrinting/ObjectPrinter.cs @@ -91,7 +91,7 @@ private string ProcessMember( var prefix = $"{indent}{member.Name} = "; if (value is null) - return prefix + null + NewLine; + return prefix + "null" + NewLine; if (settings.ExcludedTypes.Contains(value.GetType())) return string.Empty; diff --git a/Tests/TestEntities/CollectionTestsClass.cs b/Tests/TestEntities/CollectionTestsClass.cs new file mode 100644 index 000000000..e194cc26e --- /dev/null +++ b/Tests/TestEntities/CollectionTestsClass.cs @@ -0,0 +1,8 @@ +namespace Tests.TestEntities; + +public class CollectionTestsClass +{ + public string Name { get; set; } = null!; + public int Age { get; set; } + public List Friends { get; set; } = new(); +} \ No newline at end of file diff --git a/Tests/TestEntities/CultureTestClass.cs b/Tests/TestEntities/CultureTestClass.cs new file mode 100644 index 000000000..1afe2acfd --- /dev/null +++ b/Tests/TestEntities/CultureTestClass.cs @@ -0,0 +1,8 @@ +namespace Tests.TestEntities; + +public class CultureTestClass +{ + public decimal Decimal { get; set; } + public DateTime Date { get; set; } + public double Double { get; set; } +} diff --git a/Tests/TestEntities/CustomSerializerTestsClass.cs b/Tests/TestEntities/CustomSerializerTestsClass.cs new file mode 100644 index 000000000..8a881a0b2 --- /dev/null +++ b/Tests/TestEntities/CustomSerializerTestsClass.cs @@ -0,0 +1,10 @@ +namespace Tests.TestEntities; + +public class CustomSerializerTestsClass +{ + public Guid Id { get; set; } + public string Name { get; set; } = null!; + public int Age { get; set; } + public decimal Price { get; set; } + public decimal Discount { get; set; } +} \ No newline at end of file diff --git a/Tests/TestEntities/Departmnet.cs b/Tests/TestEntities/Departmnet.cs new file mode 100644 index 000000000..ef4f32a17 --- /dev/null +++ b/Tests/TestEntities/Departmnet.cs @@ -0,0 +1,6 @@ +namespace Tests.TestEntities; + +public class Department +{ + public string Name { get; set; } = null!; +} \ No newline at end of file diff --git a/Tests/TestEntities/Node.cs b/Tests/TestEntities/Node.cs index d9c0fb404..8371e6db6 100644 --- a/Tests/TestEntities/Node.cs +++ b/Tests/TestEntities/Node.cs @@ -2,7 +2,7 @@ namespace Tests.TestEntities; public class Node { - public string Value { get; set; } + public string Value { get; set; } = null!; public Node? Next { get; set; } public static Node CreateChain(int length) diff --git a/Tests/TestEntities/Person.cs b/Tests/TestEntities/Person.cs index 8bce77f84..f2226102b 100644 --- a/Tests/TestEntities/Person.cs +++ b/Tests/TestEntities/Person.cs @@ -3,7 +3,7 @@ public class Person { public Guid Id { get; set; } - public string Name { get; set; } + public string Name { get; set; } = null!; public double Height { get; set; } public int Age { get; set; } } diff --git a/Tests/TestEntities/SuperComplexClass.cs b/Tests/TestEntities/SuperComplexClass.cs index 94af9990c..066b13586 100644 --- a/Tests/TestEntities/SuperComplexClass.cs +++ b/Tests/TestEntities/SuperComplexClass.cs @@ -2,8 +2,8 @@ namespace Tests.TestEntities; public class SuperComplexClass { - public string ShortName { get; set; } - public string VeryLongDescription { get; set; } + public string ShortName { get; set; } = null!; + public string VeryLongDescription { get; set; } = null!; public int Age { get; set; } public double Weight { get; set; } public decimal Salary { get; set; } diff --git a/Tests/TestEntities/TestClass.cs b/Tests/TestEntities/TestClass.cs index 7fe830d0d..faca63909 100644 --- a/Tests/TestEntities/TestClass.cs +++ b/Tests/TestEntities/TestClass.cs @@ -3,6 +3,6 @@ namespace Tests.TestEntities; public class TestClass { - public string Property { get; set; } - public string Field; + public string Property { get; set; } = null!; + public string field; } diff --git a/Tests/TestEntities/TrimTestsClass.cs b/Tests/TestEntities/TrimTestsClass.cs new file mode 100644 index 000000000..7fc139ad3 --- /dev/null +++ b/Tests/TestEntities/TrimTestsClass.cs @@ -0,0 +1,8 @@ +namespace Tests.TestEntities; + +public class TrimTestsClass +{ + public string Short { get; set; } = null!; + public string Long { get; set; } = null!; + public string Description { get; set; } = null!; +} \ No newline at end of file diff --git a/Tests/Tests/ObjectPrinterAcceptanceTests.cs b/Tests/Tests/ObjectPrinterAcceptanceTests.cs index b8cb9f3d4..668684aad 100644 --- a/Tests/Tests/ObjectPrinterAcceptanceTests.cs +++ b/Tests/Tests/ObjectPrinterAcceptanceTests.cs @@ -3,7 +3,6 @@ using ObjectPrinting.PrintingConfigs.Extensions; using Tests.TestEntities; - namespace Tests.Tests { [TestFixture] diff --git a/Tests/Tests/ObjectPrintingTests.CollectionTests.CollectionTestsClass.cs b/Tests/Tests/ObjectPrintingTests.CollectionTests.CollectionTestsClass.cs deleted file mode 100644 index 1945e2366..000000000 --- a/Tests/Tests/ObjectPrintingTests.CollectionTests.CollectionTestsClass.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Tests.Tests; - -public partial class ObjectPrintingTests -{ - public partial class CollectionTests - { - private class CollectionTestsClass - { - public string Name { get; set; } - public int Age { get; set; } - public List Friends { get; set; } = new(); - } - } -} \ No newline at end of file diff --git a/Tests/Tests/ObjectPrintingTests.CollectionTests.cs b/Tests/Tests/ObjectPrintingTests.CollectionTests.cs index ec5050623..ae5954d27 100644 --- a/Tests/Tests/ObjectPrintingTests.CollectionTests.cs +++ b/Tests/Tests/ObjectPrintingTests.CollectionTests.cs @@ -1,18 +1,14 @@ using FluentAssertions; using ObjectPrinting; +using Tests.TestEntities; namespace Tests.Tests; public partial class ObjectPrintingTests { [TestFixture] - public partial class CollectionTests + public class CollectionTests { - private class Department - { - public string Name { get; set; } - } - [Test] public void PrintToString_Array_ShouldPrintEachElement() { diff --git a/Tests/Tests/ObjectPrintingTests.CultureTests.cs b/Tests/Tests/ObjectPrintingTests.CultureTests.cs index 34b3618a2..3313c20ba 100644 --- a/Tests/Tests/ObjectPrintingTests.CultureTests.cs +++ b/Tests/Tests/ObjectPrintingTests.CultureTests.cs @@ -2,6 +2,7 @@ using FluentAssertions; using ObjectPrinting; using ObjectPrinting.PrintingConfigs.Extensions; +using Tests.TestEntities; namespace Tests.Tests; @@ -10,13 +11,6 @@ public partial class ObjectPrintingTests [TestFixture] public class CultureTests { - public class CultureTestClass - { - public decimal Decimal { get; set; } - public DateTime Date { get; set; } - public double Double { get; set; } - } - private CultureTestClass testData; [SetUp] diff --git a/Tests/Tests/ObjectPrintingTests.CustomSerializersTests.cs b/Tests/Tests/ObjectPrintingTests.CustomSerializersTests.cs index 903e3c671..1484d89ec 100644 --- a/Tests/Tests/ObjectPrintingTests.CustomSerializersTests.cs +++ b/Tests/Tests/ObjectPrintingTests.CustomSerializersTests.cs @@ -2,6 +2,7 @@ using FluentAssertions; using ObjectPrinting; using ObjectPrinting.PrintingConfigs.Extensions; +using Tests.TestEntities; namespace Tests.Tests; @@ -10,15 +11,6 @@ public partial class ObjectPrintingTests [TestFixture] public class CustomSerializerTests { - private class CustomSerializerTestsClass - { - public Guid Id { get; set; } - public string Name { get; set; } - public int Age { get; set; } - public decimal Price { get; set; } - public decimal Discount { get; set; } - } - private CustomSerializerTestsClass testData; [SetUp] @@ -100,7 +92,7 @@ public void PrintToString_CustomSerializerForOneProperty_ShouldNotAffectOtherPro var result = printer.PrintToString(testData); result.Should().Contain("Price = $19,99") - .And.Contain("Discount = 0,1") + .And.Contain("Discount = 0.1") .And.Contain("Name = John"); } diff --git a/Tests/Tests/ObjectPrintingTests.ExcludingTests.cs b/Tests/Tests/ObjectPrintingTests.ExcludingTests.cs index 28a066488..b53522c96 100644 --- a/Tests/Tests/ObjectPrintingTests.ExcludingTests.cs +++ b/Tests/Tests/ObjectPrintingTests.ExcludingTests.cs @@ -35,7 +35,7 @@ public void PrintToString_ExcludedType_ShouldNotSerializeMembersOfThatType() [Test] public void PrintToString_ExcludedType_ShouldNotSerializeAllMembersOfThatType() { - var obj = new TestClass { Property = "Prop", Field = "Field" }; + var obj = new TestClass { Property = "Prop", field = "Field" }; var printer = ObjectPrinter.For() .Excluding() @@ -60,9 +60,9 @@ public void PrintToString_ExcludedProperty_ShouldNotSerializeProperty() [Test] public void PrintToString_ExcludedField_ShouldNotSerializeField() { - var obj = new TestClass { Property = "Prop", Field = "Field" }; + var obj = new TestClass { Property = "Prop", field = "Field" }; - var result = obj.Print(c => c.Excluding(o => o.Field)); + var result = obj.Print(c => c.Excluding(o => o.field)); result.Should().Contain("Property").And.NotContain("Field"); } diff --git a/Tests/Tests/ObjectPrintingTests.TrimTests.cs b/Tests/Tests/ObjectPrintingTests.TrimTests.cs index 62b97e9f2..859f6382a 100644 --- a/Tests/Tests/ObjectPrintingTests.TrimTests.cs +++ b/Tests/Tests/ObjectPrintingTests.TrimTests.cs @@ -1,5 +1,6 @@ using FluentAssertions; using ObjectPrinting; +using Tests.TestEntities; namespace Tests.Tests; @@ -8,13 +9,6 @@ public partial class ObjectPrintingTests [TestFixture] public class TrimTests { - private class TrimTestsClass - { - public string Short { get; set; } - public string Long { get; set; } - public string Description { get; set; } - } - [Test] public void PrintToString_TrimToZeroLength_ShouldReturnEmptyString() { From 6c58e6facaf0f04ae04cd721c315213567867fb2 Mon Sep 17 00:00:00 2001 From: Vladimir Golubev Date: Sun, 23 Nov 2025 16:41:01 +0500 Subject: [PATCH 32/41] fix: add more understandable line output --- Tests/Tests/ObjectPrintingTests.TrimTests.cs | 63 ++++++++++++++++++-- 1 file changed, 57 insertions(+), 6 deletions(-) diff --git a/Tests/Tests/ObjectPrintingTests.TrimTests.cs b/Tests/Tests/ObjectPrintingTests.TrimTests.cs index 859f6382a..b35e8c491 100644 --- a/Tests/Tests/ObjectPrintingTests.TrimTests.cs +++ b/Tests/Tests/ObjectPrintingTests.TrimTests.cs @@ -16,11 +16,17 @@ public void PrintToString_TrimToZeroLength_ShouldReturnEmptyString() var printer = ObjectPrinter.For() .Printing(p => p.Long).TrimToLength(0) .CreatePrinter(); - var result = printer.PrintToString(data); + + var expected = $""" + TrimTestsClass + {'\t'}Short = null + {'\t'}Long = + {'\t'}Description = null - result.Should().Contain("Long = ") - .And.NotContain("Very"); + """; + + result.Should().Be(expected); } [Test] @@ -37,12 +43,57 @@ public void PrintToString_TrimMultipleStringProperties_ShouldTrimEachIndependent .Printing(p => p.Long).TrimToLength(10) .Printing(p => p.Description).TrimToLength(6) .CreatePrinter(); + var result = printer.PrintToString(data); + + var expected = $""" + TrimTestsClass + {'\t'}Short = Sho + {'\t'}Long = Very long + {'\t'}Description = Medium + + """; + result.Should().Be(expected); + } + + [Test] + public void PrintToString_TrimToLengthGreaterThanString_ShouldReturnOriginalString() + { + var data = new TrimTestsClass { Short = "Hi" }; + var printer = ObjectPrinter.For() + .Printing(p => p.Short).TrimToLength(10) + .CreatePrinter(); var result = printer.PrintToString(data); + + var expected = $""" + TrimTestsClass + {'\t'}Short = Hi + {'\t'}Long = null + {'\t'}Description = null + + """; + + result.Should().Be(expected); + } + + [Test] + public void PrintToString_TrimNullString_ShouldHandleGracefully() + { + var data = new TrimTestsClass { Short = null }; + var printer = ObjectPrinter.For() + .Printing(p => p.Short).TrimToLength(5) + .CreatePrinter(); + var result = printer.PrintToString(data); + + var expected = $""" + TrimTestsClass + {'\t'}Short = null + {'\t'}Long = null + {'\t'}Description = null + + """; - result.Should().Contain("Short = Sho") - .And.Contain("Long = Very long ") - .And.Contain("Description = Medium"); + result.Should().Be(expected); } [Test] From 77dc1270b5fbc30d2d6d35a436c012dbef9b8b97 Mon Sep 17 00:00:00 2001 From: Vladimir Golubev Date: Sun, 23 Nov 2025 16:55:12 +0500 Subject: [PATCH 33/41] fix: add more understandable line output for collection tests --- .../ObjectPrintingTests.CollectionTests.cs | 252 ++++++++++++------ 1 file changed, 165 insertions(+), 87 deletions(-) diff --git a/Tests/Tests/ObjectPrintingTests.CollectionTests.cs b/Tests/Tests/ObjectPrintingTests.CollectionTests.cs index ae5954d27..ad9309277 100644 --- a/Tests/Tests/ObjectPrintingTests.CollectionTests.cs +++ b/Tests/Tests/ObjectPrintingTests.CollectionTests.cs @@ -1,106 +1,184 @@ -using FluentAssertions; -using ObjectPrinting; -using Tests.TestEntities; + using System.Collections; + using System.Collections.ObjectModel; + using FluentAssertions; + using ObjectPrinting; + using Tests.TestEntities; -namespace Tests.Tests; + namespace Tests.Tests; -public partial class ObjectPrintingTests -{ - [TestFixture] - public class CollectionTests + public partial class ObjectPrintingTests { - [Test] - public void PrintToString_Array_ShouldPrintEachElement() + [TestFixture] + public class CollectionTests { - var data = new[] { 1, 2, 3 }; - var printer = ObjectPrinter.For().CreatePrinter(); - - var result = printer.PrintToString(data); - - result.Should() - .Contain("[") - .And.Contain("1") - .And.Contain("2") - .And.Contain("3") - .And.Contain("]"); - } + [Test] + public void PrintToString_Array_ShouldPrintEachElement() + { + var data = new[] { 1, 2, 3 }; + var printer = ObjectPrinter.For().CreatePrinter(); + var result = printer.PrintToString(data); + + var expected = $""" + [ + {'\t'}1 + {'\t'}2 + {'\t'}3 + ] - [Test] - public void PrintToString_ArrayOfObjects_ShouldSerializeEachElement() - { - var data = new[] + """; + + result.Should().Be(expected); + } + + [Test] + public void PrintToString_ArrayOfObjects_ShouldSerializeEachElement() { - new CollectionTestsClass { Name = "Alice", Age = 25 }, - new CollectionTestsClass { Name = "Bob", Age = 30 } - }; - var printer = ObjectPrinter.For().CreatePrinter(); - - var result = printer.PrintToString(data); + var data = new[] + { + new CollectionTestsClass { Name = "Alice", Age = 25 }, + new CollectionTestsClass { Name = "Bob", Age = 30 } + }; + var printer = ObjectPrinter.For().CreatePrinter(); + var result = printer.PrintToString(data); + + var expected = $""" + [ + {'\t'}CollectionTestsClass + {'\t'}{'\t'}Name = Alice + {'\t'}{'\t'}Age = 25 + {'\t'}{'\t'}Friends = [ + {'\t'}{'\t'}] + {'\t'}CollectionTestsClass + {'\t'}{'\t'}Name = Bob + {'\t'}{'\t'}Age = 30 + {'\t'}{'\t'}Friends = [ + {'\t'}{'\t'}] + ] - result.Should().Contain("[") - .And.Contain("CollectionTestsClass", Exactly.Twice()) - .And.Contain("]"); - } - - [Test] - public void PrintToString_Dictionary_ShoulPrintKeysAndValues() - { - var dict = new Dictionary + """; + + result.Should().Be(expected); + } + + [Test] + public void PrintToString_Dictionary_ShouldPrintKeysAndValues() { - [1] = "1", - [2] = "2" - }; + var dict = new Dictionary + { + [1] = "1", + [2] = "2" + }; + var printer = ObjectPrinter.For>().CreatePrinter(); + var result = printer.PrintToString(dict); + + var expected = $$""" + { + {{'\t'}}[1] = 1 + {{'\t'}}[2] = 2 + } - var printer = ObjectPrinter.For>().CreatePrinter(); - var result = printer.PrintToString(dict); + """; - result.Should() - .Contain("[1] = 1") - .And.Contain("[2] = 2"); - } + result.Should().Be(expected); + } - [Test] - public void PrintToString_DictionaryWithComplexObjects_ShouldSerializeKeysAndValues() - { - var dict = new Dictionary + [Test] + public void PrintToString_DictionaryWithComplexObjects_ShouldSerializeKeysAndValues() { - [new CollectionTestsClass { Name = "Manager" }] = new Department { Name = "HR" }, - [new CollectionTestsClass { Name = "Developer" }] = new Department { Name = "IT" } - }; + var dict = new Dictionary + { + [new CollectionTestsClass { Name = "Manager" }] = new() { Name = "HR" }, + [new CollectionTestsClass { Name = "Developer" }] = new() { Name = "IT" } + }; - var printer = ObjectPrinter - .For>() - .CreatePrinter(); - var result = printer.PrintToString(dict); - - result.Should().MatchRegex( - @"\[\s*CollectionTestsClass[\s\S]*?\]\s*=\s*Department[\s\S]*?" + - @"\[\s*CollectionTestsClass[\s\S]*?\]\s*=\s*Department"); - } + var printer = ObjectPrinter + .For>() + .CreatePrinter(); + var result = printer.PrintToString(dict); + + var expected = $$""" + { + {{'\t'}}[CollectionTestsClass + {{'\t'}}{{'\t'}}Name = Manager + {{'\t'}}{{'\t'}}Age = 0 + {{'\t'}}{{'\t'}}Friends = [ + {{'\t'}}{{'\t'}}]] = Department + {{'\t'}}{{'\t'}}Name = HR + {{'\t'}}[CollectionTestsClass + {{'\t'}}{{'\t'}}Name = Developer + {{'\t'}}{{'\t'}}Age = 0 + {{'\t'}}{{'\t'}}Friends = [ + {{'\t'}}{{'\t'}}]] = Department + {{'\t'}}{{'\t'}}Name = IT + } - [Test] - public void PrintToString_EmptyCollection_ShouldShowEmptyStructure() - { - var data = new CollectionTestsClass { Name = "Test", Friends = new List() }; - var printer = ObjectPrinter - .For() - .CreatePrinter(); - var result = printer.PrintToString(data); + """; + + result.Should().Be(expected); + } + - result.Should().Contain("Friends = ["); - } + [Test] + public void PrintToString_NullCollection_ShouldShowNull() + { + var data = new CollectionTestsClass { Name = "Test", Friends = null }; + var printer = ObjectPrinter + .For() + .CreatePrinter(); + var result = printer.PrintToString(data); + + var expected = $""" + CollectionTestsClass + {'\t'}Name = Test + {'\t'}Age = 0 + {'\t'}Friends = null + + """; + - [Test] - public void PrintToString_NullCollection_ShouldShowNull() - { - var data = new CollectionTestsClass() { Name = "Test", Friends = null }; - var printer = ObjectPrinter - .For() - .CreatePrinter(); - var result = printer.PrintToString(data); + result.Should().Be(expected); + } + + private static IEnumerable EmptyCollectionsTestCases + { + get + { + yield return new TestCaseData(Array.Empty()) + .SetName("EmptyArray_ShouldShowEmptyArrayStructure"); + + yield return new TestCaseData(new List()) + .SetName("EmptyList_ShouldShowEmptyListStructure"); + + yield return new TestCaseData(new HashSet()) + .SetName("EmptyHashSet_ShouldShowEmptyHashSetStructure"); + + yield return new TestCaseData(new Collection()) + .SetName("EmptyCollection_ShouldShowEmptyCollectionStructure"); + } + } + + [Test] + [TestCaseSource(nameof(EmptyCollectionsTestCases))] + public void PrintToString_EmptyCollections_ShouldShowEmptyStructure( + IEnumerable emptyCollection) + { + var printer = ObjectPrinter.For().CreatePrinter(); + var result = printer.PrintToString(emptyCollection); + + var expected = $"[{Environment.NewLine}]{Environment.NewLine}"; + + result.Should().Be(expected); + } - result.Should().Contain("Friends = null"); + [Test] + public void PrintToString_EmptyDictionary_ShouldShowEmptyStructure() + { + var printer = ObjectPrinter.For>().CreatePrinter(); + var result = printer.PrintToString(new Dictionary()); + + var expected = $"{{{Environment.NewLine}}}{Environment.NewLine}"; + result.Should().Be(expected); + } } - } -} \ No newline at end of file + } \ No newline at end of file From 6dc2bfa7279a8cabceb648761d0513142d0bdfdf Mon Sep 17 00:00:00 2001 From: Vladimir Golubev Date: Sun, 23 Nov 2025 16:58:47 +0500 Subject: [PATCH 34/41] fix: add more understandable line output for culture tests --- .../Tests/ObjectPrintingTests.CultureTests.cs | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/Tests/Tests/ObjectPrintingTests.CultureTests.cs b/Tests/Tests/ObjectPrintingTests.CultureTests.cs index 3313c20ba..7b9953f2a 100644 --- a/Tests/Tests/ObjectPrintingTests.CultureTests.cs +++ b/Tests/Tests/ObjectPrintingTests.CultureTests.cs @@ -30,10 +30,17 @@ public void PrintToString_CustomCulture_ShouldApplyFormatting() var printer = ObjectPrinter.For() .Printing().Using(new CultureInfo("de-DE")) .CreatePrinter(); - var result = printer.PrintToString(testData); + + var expected = $""" + CultureTestClass + {'\t'}Decimal = 1234,56 + {'\t'}Date = 10/15/2023 14:30:00 + {'\t'}Double = 1234.567 + + """; - result.Should().Contain("Decimal = 1234,56"); + result.Should().Be(expected); } [Test] @@ -46,11 +53,16 @@ public void PrintToString_MultipleCultures_ShouldApplyAll() .CreatePrinter(); var result = printer.PrintToString(testData); + + var expected = $""" + CultureTestClass + {'\t'}Decimal = 1234.56 + {'\t'}Date = 15/10/2023 14:30:00 + {'\t'}Double = 1234,567 - result.Should() - .Contain("Decimal = 1234.56") - .And.Contain("Date = 15/10/2023 14:30:00") - .And.Contain("Double = 1234,567"); + """; + + result.Should().Be(expected); } } } \ No newline at end of file From caf6b1a5860e908e8b2aa87a92f2e8c99904f897 Mon Sep 17 00:00:00 2001 From: Vladimir Golubev Date: Sun, 23 Nov 2025 17:07:54 +0500 Subject: [PATCH 35/41] fix: add more understandable line output for serializers tests --- ...ectPrintingTests.CustomSerializersTests.cs | 116 ++++++++++++++---- 1 file changed, 89 insertions(+), 27 deletions(-) diff --git a/Tests/Tests/ObjectPrintingTests.CustomSerializersTests.cs b/Tests/Tests/ObjectPrintingTests.CustomSerializersTests.cs index 1484d89ec..50e870249 100644 --- a/Tests/Tests/ObjectPrintingTests.CustomSerializersTests.cs +++ b/Tests/Tests/ObjectPrintingTests.CustomSerializersTests.cs @@ -35,8 +35,19 @@ public void PrintToString_CustomTypeSerializer_ShouldFormatType() .CreatePrinter(); var result = printer.PrintToString(testData); - - result.Should().Contain("Id = A1B2C3D4"); + + var expected = $""" + CustomSerializerTestsClass + {'\t'}Id = A1B2C3D4 + {'\t'}Name = John + {'\t'}Age = 25 + {'\t'}Price = 19.99 + {'\t'}Discount = 0.1 + + """; + + + result.Should().Be(expected); } [Test] @@ -44,13 +55,22 @@ public void PrintToString_CustomPropertySerializer_ShouldOverrideTypeSerializer( { var printer = ObjectPrinter.For() .Printing().Using(i => $"{i} years") - .Printing(p => p.Age).Using(age => $"Age: {age}") + .Printing(p => p.Age).Using(age => $"***{age}***") .CreatePrinter(); var result = printer.PrintToString(testData); - - result.Should().Contain("Age = Age: 25") - .And.NotContain("Age = 25 years"); + + var expected = $""" + CustomSerializerTestsClass + {'\t'}Id = a1b2c3d4-e5f6-7890-abcd-ef1234567890 + {'\t'}Name = John + {'\t'}Age = ***25*** + {'\t'}Price = 19.99 + {'\t'}Discount = 0.1 + + """; + + result.Should().Be(expected); } [Test] @@ -61,8 +81,18 @@ public void PrintToString_CustomSerializerReturnsNull_ShouldHandleCorrectly() .CreatePrinter(); var result = printer.PrintToString(testData); - - result.Should().Contain("Name = "); + + var expected = $""" + CustomSerializerTestsClass + {'\t'}Id = a1b2c3d4-e5f6-7890-abcd-ef1234567890 + {'\t'}Name = + {'\t'}Age = 25 + {'\t'}Price = 19.99 + {'\t'}Discount = 0.1 + + """; + + result.Should().Be(expected); } [Test] @@ -76,10 +106,17 @@ public void PrintToString_CustomSerializerForMultipleTypes_ShouldApplyAll() var result = printer.PrintToString(testData); - result.Should() - .Contain("Id = a1b2c3d4") - .And.Contain("Age = #25") - .And.Contain("Price = $19,99"); + var expected = $""" + CustomSerializerTestsClass + {'\t'}Id = a1b2c3d4 + {'\t'}Name = John + {'\t'}Age = #25 + {'\t'}Price = $19,99 + {'\t'}Discount = $0,1 + + """; + + result.Should().Be(expected); } [Test] @@ -91,9 +128,17 @@ public void PrintToString_CustomSerializerForOneProperty_ShouldNotAffectOtherPro var result = printer.PrintToString(testData); - result.Should().Contain("Price = $19,99") - .And.Contain("Discount = 0.1") - .And.Contain("Name = John"); + var expected = $""" + CustomSerializerTestsClass + {'\t'}Id = a1b2c3d4-e5f6-7890-abcd-ef1234567890 + {'\t'}Name = John + {'\t'}Age = 25 + {'\t'}Price = $19,99 + {'\t'}Discount = 0.1 + + """; + + result.Should().Be(expected); } [Test] @@ -106,25 +151,42 @@ public void PrintToString_MultiplePropertySerializers_ShouldApplyEachToRespectiv .CreatePrinter(); var result = printer.PrintToString(testData); - - result.Should().Contain("Name = JOHN") - .And.Contain("Age = 25 years") - .And.Contain("Price = $19,99"); + + + var expected = $""" + CustomSerializerTestsClass + {'\t'}Id = a1b2c3d4-e5f6-7890-abcd-ef1234567890 + {'\t'}Name = JOHN + {'\t'}Age = 25 years + {'\t'}Price = $19,99 + {'\t'}Discount = 0.1 + + """; + + result.Should().Be(expected); } [Test] public void PrintToString_CustomSerializerDominatesOverCulture() { - var data = new { Price = 1234.56d, Weight = 7.89d }; - var printer = ObjectPrinter.For() - .Printing().Using(new CultureInfo("de-DE")) - .Printing().Using(p => $"${p:0.00}") + var printer = ObjectPrinter.For() + .Printing().Using(new CultureInfo("de-DE")) + .Printing().Using(p => $"${p:0.00}") .CreatePrinter(); - var result = printer.PrintToString(data); - - result.Should().Contain("Price = $1234,56") - .And.Contain("Weight = $7,89"); + var result = printer.PrintToString(testData); + + var expected = $""" + CustomSerializerTestsClass + {'\t'}Id = a1b2c3d4-e5f6-7890-abcd-ef1234567890 + {'\t'}Name = John + {'\t'}Age = 25 + {'\t'}Price = $19,99 + {'\t'}Discount = $0,10 + + """; + + result.Should().Be(expected); } } } \ No newline at end of file From d986cf87de2127d8f6177ded786c1cdbac09f486 Mon Sep 17 00:00:00 2001 From: Vladimir Golubev Date: Sun, 23 Nov 2025 17:17:12 +0500 Subject: [PATCH 36/41] fix: add more understandable line output for excluding tests --- .../ObjectPrintingTests.ExcludingTests.cs | 51 ++++++++++++------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/Tests/Tests/ObjectPrintingTests.ExcludingTests.cs b/Tests/Tests/ObjectPrintingTests.ExcludingTests.cs index b53522c96..c5eefd24b 100644 --- a/Tests/Tests/ObjectPrintingTests.ExcludingTests.cs +++ b/Tests/Tests/ObjectPrintingTests.ExcludingTests.cs @@ -28,22 +28,18 @@ public void PrintToString_ExcludedType_ShouldNotSerializeMembersOfThatType() .CreatePrinter(); var result = printer.PrintToString(person); + + var expected = $""" + Person + {'\t'}Id = 00000000-0000-0000-0000-000000000000 + {'\t'}Name = Alex + {'\t'}Age = 19 - result.Should().NotContain("Height"); - } - - [Test] - public void PrintToString_ExcludedType_ShouldNotSerializeAllMembersOfThatType() - { - var obj = new TestClass { Property = "Prop", field = "Field" }; - - var printer = ObjectPrinter.For() - .Excluding() - .CreatePrinter(); - - var result = printer.PrintToString(obj); - result.Should().NotContain("Prop").And.NotContain("Field"); + """; + + result.Should().Be(expected); } + [Test] public void PrintToString_ExcludedProperty_ShouldNotSerializeProperty() @@ -53,8 +49,16 @@ public void PrintToString_ExcludedProperty_ShouldNotSerializeProperty() .CreatePrinter(); var result = printer.PrintToString(person); + + var expected = $""" + Person + {'\t'}Id = 00000000-0000-0000-0000-000000000000 + {'\t'}Name = Alex + {'\t'}Height = 175.123 - result.Should().NotContain("Age"); + """; + + result.Should().Be(expected); } [Test] @@ -64,7 +68,13 @@ public void PrintToString_ExcludedField_ShouldNotSerializeField() var result = obj.Print(c => c.Excluding(o => o.field)); - result.Should().Contain("Property").And.NotContain("Field"); + var expected = $""" + TestClass + {'\t'}Property = Prop + + """; + + result.Should().Be(expected); } [Test] @@ -76,7 +86,14 @@ public void PrintToString_ExcludedMultipleMembers_ShouldNotSerializeMultipleMemb .CreatePrinter(); var result = printer.PrintToString(person); - result.Should().NotContain("Id").And.NotContain("Age"); + var expected = $""" + Person + {'\t'}Name = Alex + {'\t'}Height = 175.123 + + """; + + result.Should().Be(expected); } [Test] From 8e4111d09ad22a28dbec29738f758f94c404ca44 Mon Sep 17 00:00:00 2001 From: Vladimir Golubev Date: Sun, 23 Nov 2025 17:20:59 +0500 Subject: [PATCH 37/41] test: add test to check excluding domination over printing + using --- .../ObjectPrintingTests.ExcludingTests.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Tests/Tests/ObjectPrintingTests.ExcludingTests.cs b/Tests/Tests/ObjectPrintingTests.ExcludingTests.cs index c5eefd24b..ba4c5b4e9 100644 --- a/Tests/Tests/ObjectPrintingTests.ExcludingTests.cs +++ b/Tests/Tests/ObjectPrintingTests.ExcludingTests.cs @@ -120,5 +120,26 @@ public void PrintToString_AllPropertiesExcluded_ShouldShowTypeName() result.Should().Be("Person" + Environment.NewLine); } + + [Test] + public void PrintToString_Excluding_ShouldDominateOnPrinting() + { + var printer = ObjectPrinter.For() + .Excluding(p => p.Id) + .Printing(p => p.Id).Using(p => "id") + .CreatePrinter(); + var result = printer.PrintToString(person); + + var expected = $""" + Person + {'\t'}Name = Alex + {'\t'}Height = 175.123 + {'\t'}Age = 19 + + """; + + result.Should().Be(expected); + + } } } \ No newline at end of file From e891255ab1ae8aac46d50ca3f5cd1c5e67400bb9 Mon Sep 17 00:00:00 2001 From: Vladimir Golubev Date: Sun, 23 Nov 2025 17:21:33 +0500 Subject: [PATCH 38/41] test: add files to verify lib tests --- ...ect_ShouldSerializeAllMembers.verified.txt | 5 ++ ...tions_ShouldApplyAllCorrectly.verified.txt | 48 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 Tests/Tests/ObjectPrintingTests.PrintToString_SimpleObject_ShouldSerializeAllMembers.verified.txt create mode 100644 Tests/Tests/ObjectPrintingTests.PrintToString_WithMaximumConfigurations_ShouldApplyAllCorrectly.verified.txt diff --git a/Tests/Tests/ObjectPrintingTests.PrintToString_SimpleObject_ShouldSerializeAllMembers.verified.txt b/Tests/Tests/ObjectPrintingTests.PrintToString_SimpleObject_ShouldSerializeAllMembers.verified.txt new file mode 100644 index 000000000..47ca6bd8a --- /dev/null +++ b/Tests/Tests/ObjectPrintingTests.PrintToString_SimpleObject_ShouldSerializeAllMembers.verified.txt @@ -0,0 +1,5 @@ +Person + Id = 00000000-0000-0000-0000-000000000000 + Name = John + Height = 0 + Age = 25 diff --git a/Tests/Tests/ObjectPrintingTests.PrintToString_WithMaximumConfigurations_ShouldApplyAllCorrectly.verified.txt b/Tests/Tests/ObjectPrintingTests.PrintToString_WithMaximumConfigurations_ShouldApplyAllCorrectly.verified.txt new file mode 100644 index 000000000..735020a40 --- /dev/null +++ b/Tests/Tests/ObjectPrintingTests.PrintToString_WithMaximumConfigurations_ShouldApplyAllCorrectly.verified.txt @@ -0,0 +1,48 @@ +SuperComplexClass + ShortName = Jo + VeryLongDescription = This is a very long + Age = Age is 35 + Salary = Salary: 75000,50$ + BirthDate = 1988-05-15 + Tags = Tags[4] + Scores = Scores[3] + Manager = SuperComplexClass + ShortName = Ma + VeryLongDescription = null + Age = Age is 45 + Salary = Salary: 0$ + BirthDate = 0001-01-01 + Tags = Tags[0] + Scores = Scores[0] + Manager = null + Assistant = null + TeamMembers = [ + (cyclic reference) + ] + Assistant = SuperComplexClass + ShortName = Al + VeryLongDescription = null + Age = Age is 28 + Salary = Salary: 60000,00$ + BirthDate = 0001-01-01 + Tags = Tags[0] + Scores = Scores[0] + Manager = null + Assistant = null + TeamMembers = [ + ] + TeamMembers = [ + (cyclic reference) + SuperComplexClass + ShortName = Bo + VeryLongDescription = null + Age = Age is 32 + Salary = Salary: 65000,00$ + BirthDate = 0001-01-01 + Tags = Tags[0] + Scores = Scores[0] + Manager = null + Assistant = null + TeamMembers = [ + ] + ] From 88d7e9866f98f5e65e78fee602a8d42fb7322773 Mon Sep 17 00:00:00 2001 From: Vladimir Golubev Date: Sun, 23 Nov 2025 17:23:21 +0500 Subject: [PATCH 39/41] fix: fix recursion depth check --- ObjectPrinting/PrintingConfigs/PrintingConfig.cs | 2 +- Tests/Tests/ObjectPrintingTests.NestedObjectsTest.cs | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/ObjectPrinting/PrintingConfigs/PrintingConfig.cs b/ObjectPrinting/PrintingConfigs/PrintingConfig.cs index f30cf768f..a4123bfa9 100644 --- a/ObjectPrinting/PrintingConfigs/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfigs/PrintingConfig.cs @@ -18,7 +18,7 @@ public class PrintingConfig public PrintingConfig SetMaxRecursionDepth(int recursionDepth) { - if (recursionDepth <= 0) + if (recursionDepth < 0) { throw new ArgumentException("Max recursion depth must be greater than zero."); } diff --git a/Tests/Tests/ObjectPrintingTests.NestedObjectsTest.cs b/Tests/Tests/ObjectPrintingTests.NestedObjectsTest.cs index 747f89200..2a998752b 100644 --- a/Tests/Tests/ObjectPrintingTests.NestedObjectsTest.cs +++ b/Tests/Tests/ObjectPrintingTests.NestedObjectsTest.cs @@ -53,13 +53,12 @@ public void PrintToString_WhenReachesMaxRecursionLevel_ShouldStop() .And.NotContain("level16"); } - [TestCase(0)] - [TestCase(-1)] - public void SetMaxRecursionDepth_NotPositiveValue_ShouldThrowArgumentException(int maxDepth) + [Test] + public void SetMaxRecursionDepth_NegativeValue_ShouldThrowArgumentException() { var printer = ObjectPrinter.For(); - var act = () => printer.SetMaxRecursionDepth(maxDepth); + var act = () => printer.SetMaxRecursionDepth(-1); act.Should().Throw(); } From 56f789e42301da0251280933062e78001ede358a Mon Sep 17 00:00:00 2001 From: Vladimir Golubev Date: Sun, 23 Nov 2025 17:25:12 +0500 Subject: [PATCH 40/41] fix: rename create method --- .../PrintingConfigs/Extensions/ObjectExtensions.cs | 4 ++-- ObjectPrinting/PrintingConfigs/PrintingConfig.cs | 4 ++-- Tests/Tests/ObjectPrinterAcceptanceTests.cs | 2 +- Tests/Tests/ObjectPrintingTests.CollectionTests.cs | 14 +++++++------- Tests/Tests/ObjectPrintingTests.CultureTests.cs | 4 ++-- .../ObjectPrintingTests.CustomSerializersTests.cs | 14 +++++++------- Tests/Tests/ObjectPrintingTests.ExcludingTests.cs | 10 +++++----- .../Tests/ObjectPrintingTests.NestedObjectsTest.cs | 6 +++--- Tests/Tests/ObjectPrintingTests.TrimTests.cs | 8 ++++---- Tests/Tests/ObjectPrintingTests.cs | 2 +- 10 files changed, 34 insertions(+), 34 deletions(-) diff --git a/ObjectPrinting/PrintingConfigs/Extensions/ObjectExtensions.cs b/ObjectPrinting/PrintingConfigs/Extensions/ObjectExtensions.cs index 42ad97135..152d0798a 100644 --- a/ObjectPrinting/PrintingConfigs/Extensions/ObjectExtensions.cs +++ b/ObjectPrinting/PrintingConfigs/Extensions/ObjectExtensions.cs @@ -6,12 +6,12 @@ public static class ObjectExtensions { public static string Print(this T obj) { - return ObjectPrinter.For().CreatePrinter().PrintToString(obj); + return ObjectPrinter.For().Create().PrintToString(obj); } public static string Print(this T obj, Func, PrintingConfig> config) { - return config(ObjectPrinter.For()).CreatePrinter().PrintToString(obj); + return config(ObjectPrinter.For()).Create().PrintToString(obj); } } diff --git a/ObjectPrinting/PrintingConfigs/PrintingConfig.cs b/ObjectPrinting/PrintingConfigs/PrintingConfig.cs index a4123bfa9..deba632f5 100644 --- a/ObjectPrinting/PrintingConfigs/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfigs/PrintingConfig.cs @@ -84,7 +84,7 @@ public PrintingConfig TrimMember(Expression> select return this; } - public PrinterSettings CreateSettings() + private PrinterSettings CreateSettings() { return new PrinterSettings( maxRecursionDepth, @@ -97,7 +97,7 @@ public PrinterSettings CreateSettings() ); } - public ObjectPrinter CreatePrinter() + public ObjectPrinter Create() { return new ObjectPrinter(CreateSettings()); } diff --git a/Tests/Tests/ObjectPrinterAcceptanceTests.cs b/Tests/Tests/ObjectPrinterAcceptanceTests.cs index 668684aad..9d997bed4 100644 --- a/Tests/Tests/ObjectPrinterAcceptanceTests.cs +++ b/Tests/Tests/ObjectPrinterAcceptanceTests.cs @@ -20,7 +20,7 @@ public void Demo() .Printing(p => p.Age).Using(a => $"AGE : {a}") // 4. Настроить сериализацию конкретного свойства .Printing(p => p.Name).TrimToLength(10) // 5. Настроить обрезание строковых свойств .Excluding(p => p.Age) // 6. Исключить конкретное свойство - .CreatePrinter(); + .Create(); var s1 = printer.PrintToString(person); diff --git a/Tests/Tests/ObjectPrintingTests.CollectionTests.cs b/Tests/Tests/ObjectPrintingTests.CollectionTests.cs index ad9309277..7bef2692a 100644 --- a/Tests/Tests/ObjectPrintingTests.CollectionTests.cs +++ b/Tests/Tests/ObjectPrintingTests.CollectionTests.cs @@ -15,7 +15,7 @@ public class CollectionTests public void PrintToString_Array_ShouldPrintEachElement() { var data = new[] { 1, 2, 3 }; - var printer = ObjectPrinter.For().CreatePrinter(); + var printer = ObjectPrinter.For().Create(); var result = printer.PrintToString(data); var expected = $""" @@ -38,7 +38,7 @@ public void PrintToString_ArrayOfObjects_ShouldSerializeEachElement() new CollectionTestsClass { Name = "Alice", Age = 25 }, new CollectionTestsClass { Name = "Bob", Age = 30 } }; - var printer = ObjectPrinter.For().CreatePrinter(); + var printer = ObjectPrinter.For().Create(); var result = printer.PrintToString(data); var expected = $""" @@ -68,7 +68,7 @@ public void PrintToString_Dictionary_ShouldPrintKeysAndValues() [1] = "1", [2] = "2" }; - var printer = ObjectPrinter.For>().CreatePrinter(); + var printer = ObjectPrinter.For>().Create(); var result = printer.PrintToString(dict); var expected = $$""" @@ -94,7 +94,7 @@ public void PrintToString_DictionaryWithComplexObjects_ShouldSerializeKeysAndVal var printer = ObjectPrinter .For>() - .CreatePrinter(); + .Create(); var result = printer.PrintToString(dict); var expected = $$""" @@ -125,7 +125,7 @@ public void PrintToString_NullCollection_ShouldShowNull() var data = new CollectionTestsClass { Name = "Test", Friends = null }; var printer = ObjectPrinter .For() - .CreatePrinter(); + .Create(); var result = printer.PrintToString(data); var expected = $""" @@ -163,7 +163,7 @@ private static IEnumerable EmptyCollectionsTestCases public void PrintToString_EmptyCollections_ShouldShowEmptyStructure( IEnumerable emptyCollection) { - var printer = ObjectPrinter.For().CreatePrinter(); + var printer = ObjectPrinter.For().Create(); var result = printer.PrintToString(emptyCollection); var expected = $"[{Environment.NewLine}]{Environment.NewLine}"; @@ -174,7 +174,7 @@ public void PrintToString_EmptyCollections_ShouldShowEmptyStructure( [Test] public void PrintToString_EmptyDictionary_ShouldShowEmptyStructure() { - var printer = ObjectPrinter.For>().CreatePrinter(); + var printer = ObjectPrinter.For>().Create(); var result = printer.PrintToString(new Dictionary()); var expected = $"{{{Environment.NewLine}}}{Environment.NewLine}"; diff --git a/Tests/Tests/ObjectPrintingTests.CultureTests.cs b/Tests/Tests/ObjectPrintingTests.CultureTests.cs index 7b9953f2a..b4602c9e8 100644 --- a/Tests/Tests/ObjectPrintingTests.CultureTests.cs +++ b/Tests/Tests/ObjectPrintingTests.CultureTests.cs @@ -29,7 +29,7 @@ public void PrintToString_CustomCulture_ShouldApplyFormatting() { var printer = ObjectPrinter.For() .Printing().Using(new CultureInfo("de-DE")) - .CreatePrinter(); + .Create(); var result = printer.PrintToString(testData); var expected = $""" @@ -50,7 +50,7 @@ public void PrintToString_MultipleCultures_ShouldApplyAll() .Printing().Using(new CultureInfo("es-ES")) .Printing().Using(new CultureInfo("en-US")) .Printing().Using(new CultureInfo("fr-FR")) - .CreatePrinter(); + .Create(); var result = printer.PrintToString(testData); diff --git a/Tests/Tests/ObjectPrintingTests.CustomSerializersTests.cs b/Tests/Tests/ObjectPrintingTests.CustomSerializersTests.cs index 50e870249..e39864e89 100644 --- a/Tests/Tests/ObjectPrintingTests.CustomSerializersTests.cs +++ b/Tests/Tests/ObjectPrintingTests.CustomSerializersTests.cs @@ -32,7 +32,7 @@ public void PrintToString_CustomTypeSerializer_ShouldFormatType() var printer = ObjectPrinter.For() .Printing().Using(g => g .ToString("N").Substring(0, 8).ToUpper()) - .CreatePrinter(); + .Create(); var result = printer.PrintToString(testData); @@ -56,7 +56,7 @@ public void PrintToString_CustomPropertySerializer_ShouldOverrideTypeSerializer( var printer = ObjectPrinter.For() .Printing().Using(i => $"{i} years") .Printing(p => p.Age).Using(age => $"***{age}***") - .CreatePrinter(); + .Create(); var result = printer.PrintToString(testData); @@ -78,7 +78,7 @@ public void PrintToString_CustomSerializerReturnsNull_ShouldHandleCorrectly() { var printer = ObjectPrinter.For() .Printing(p => p.Name).Using(_ => null) - .CreatePrinter(); + .Create(); var result = printer.PrintToString(testData); @@ -102,7 +102,7 @@ public void PrintToString_CustomSerializerForMultipleTypes_ShouldApplyAll() .Printing().Using(g => g.ToString().Substring(0, 8)) .Printing().Using(i => $"#{i}") .Printing().Using(p => $"${p}") - .CreatePrinter(); + .Create(); var result = printer.PrintToString(testData); @@ -124,7 +124,7 @@ public void PrintToString_CustomSerializerForOneProperty_ShouldNotAffectOtherPro { var printer = ObjectPrinter.For() .Printing(p => p.Price).Using(p => $"${p}") - .CreatePrinter(); + .Create(); var result = printer.PrintToString(testData); @@ -148,7 +148,7 @@ public void PrintToString_MultiplePropertySerializers_ShouldApplyEachToRespectiv .Printing(p => p.Name).Using(n => $"{n.ToUpper()}") .Printing(p => p.Age).Using(a => $"{a} years") .Printing(p => p.Price).Using(p => $"${p}") - .CreatePrinter(); + .Create(); var result = printer.PrintToString(testData); @@ -172,7 +172,7 @@ public void PrintToString_CustomSerializerDominatesOverCulture() var printer = ObjectPrinter.For() .Printing().Using(new CultureInfo("de-DE")) .Printing().Using(p => $"${p:0.00}") - .CreatePrinter(); + .Create(); var result = printer.PrintToString(testData); diff --git a/Tests/Tests/ObjectPrintingTests.ExcludingTests.cs b/Tests/Tests/ObjectPrintingTests.ExcludingTests.cs index ba4c5b4e9..d9687d11a 100644 --- a/Tests/Tests/ObjectPrintingTests.ExcludingTests.cs +++ b/Tests/Tests/ObjectPrintingTests.ExcludingTests.cs @@ -25,7 +25,7 @@ public void PrintToString_ExcludedType_ShouldNotSerializeMembersOfThatType() { var printer = new PrintingConfig() .Excluding() - .CreatePrinter(); + .Create(); var result = printer.PrintToString(person); @@ -46,7 +46,7 @@ public void PrintToString_ExcludedProperty_ShouldNotSerializeProperty() { var printer = new PrintingConfig() .Excluding(p => p.Age) - .CreatePrinter(); + .Create(); var result = printer.PrintToString(person); @@ -83,7 +83,7 @@ public void PrintToString_ExcludedMultipleMembers_ShouldNotSerializeMultipleMemb var printer = ObjectPrinter.For() .Excluding(p => p.Id) .Excluding(p => p.Age) - .CreatePrinter(); + .Create(); var result = printer.PrintToString(person); var expected = $""" @@ -114,7 +114,7 @@ public void PrintToString_AllPropertiesExcluded_ShouldShowTypeName() .Excluding() .Excluding(p => p.Id) .Excluding(p => p.Height) - .CreatePrinter(); + .Create(); var result = printer.PrintToString(person); @@ -127,7 +127,7 @@ public void PrintToString_Excluding_ShouldDominateOnPrinting() var printer = ObjectPrinter.For() .Excluding(p => p.Id) .Printing(p => p.Id).Using(p => "id") - .CreatePrinter(); + .Create(); var result = printer.PrintToString(person); var expected = $""" diff --git a/Tests/Tests/ObjectPrintingTests.NestedObjectsTest.cs b/Tests/Tests/ObjectPrintingTests.NestedObjectsTest.cs index 2a998752b..5c176b9da 100644 --- a/Tests/Tests/ObjectPrintingTests.NestedObjectsTest.cs +++ b/Tests/Tests/ObjectPrintingTests.NestedObjectsTest.cs @@ -15,7 +15,7 @@ public void PrintToString_NestedObject_ShouldPrintAllLevels() var node = Node.CreateChain(3); var printer = ObjectPrinter .For() - .CreatePrinter(); + .Create(); var result = printer.PrintToString(node); @@ -30,7 +30,7 @@ public void PrintToString_WithCyclicReference_ShouldNotFall() var node = Node.CreateChainWithCycle(); var printer = ObjectPrinter .For() - .CreatePrinter(); + .Create(); var act = () => printer.PrintToString(node); @@ -44,7 +44,7 @@ public void PrintToString_WhenReachesMaxRecursionLevel_ShouldStop() var node = Node.CreateChain(16); var printer = ObjectPrinter .For() - .CreatePrinter(); + .Create(); var result = printer.PrintToString(node); diff --git a/Tests/Tests/ObjectPrintingTests.TrimTests.cs b/Tests/Tests/ObjectPrintingTests.TrimTests.cs index b35e8c491..858662ca0 100644 --- a/Tests/Tests/ObjectPrintingTests.TrimTests.cs +++ b/Tests/Tests/ObjectPrintingTests.TrimTests.cs @@ -15,7 +15,7 @@ public void PrintToString_TrimToZeroLength_ShouldReturnEmptyString() var data = new TrimTestsClass { Long = "Very long text" }; var printer = ObjectPrinter.For() .Printing(p => p.Long).TrimToLength(0) - .CreatePrinter(); + .Create(); var result = printer.PrintToString(data); var expected = $""" @@ -42,7 +42,7 @@ public void PrintToString_TrimMultipleStringProperties_ShouldTrimEachIndependent .Printing(p => p.Short).TrimToLength(3) .Printing(p => p.Long).TrimToLength(10) .Printing(p => p.Description).TrimToLength(6) - .CreatePrinter(); + .Create(); var result = printer.PrintToString(data); var expected = $""" @@ -62,7 +62,7 @@ public void PrintToString_TrimToLengthGreaterThanString_ShouldReturnOriginalStri var data = new TrimTestsClass { Short = "Hi" }; var printer = ObjectPrinter.For() .Printing(p => p.Short).TrimToLength(10) - .CreatePrinter(); + .Create(); var result = printer.PrintToString(data); var expected = $""" @@ -82,7 +82,7 @@ public void PrintToString_TrimNullString_ShouldHandleGracefully() var data = new TrimTestsClass { Short = null }; var printer = ObjectPrinter.For() .Printing(p => p.Short).TrimToLength(5) - .CreatePrinter(); + .Create(); var result = printer.PrintToString(data); var expected = $""" diff --git a/Tests/Tests/ObjectPrintingTests.cs b/Tests/Tests/ObjectPrintingTests.cs index c3c69d3f4..744fef64b 100644 --- a/Tests/Tests/ObjectPrintingTests.cs +++ b/Tests/Tests/ObjectPrintingTests.cs @@ -66,7 +66,7 @@ public async Task PrintToString_WithMaximumConfigurations_ShouldApplyAllCorrectl .Printing().Using(new CultureInfo("fr-FR")) .Printing>().Using(list => $"Tags[{list.Count}]") .Printing>().Using(dict => $"Scores[{dict.Count}]") - .CreatePrinter(); + .Create(); var result = printer.PrintToString(testData); From b8fe14906bda257cd11a39c61387f998ee9f691d Mon Sep 17 00:00:00 2001 From: Vladimir Golubev Date: Mon, 24 Nov 2025 21:55:50 +0500 Subject: [PATCH 41/41] fix: fix culture in custom erializers --- ObjectPrinting/ObjectPrinter.cs | 2 +- ObjectPrinting/PrintingConfigs/StringPropertyPrintingConfig.cs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/ObjectPrinting/ObjectPrinter.cs b/ObjectPrinting/ObjectPrinter.cs index 372daa906..241b834eb 100644 --- a/ObjectPrinting/ObjectPrinter.cs +++ b/ObjectPrinting/ObjectPrinter.cs @@ -157,7 +157,7 @@ private bool TrySerializeMember(MemberInfo member, object value, out string? res { if (settings.MemberSerializers.TryGetValue(member, out var serializer)) { - result = serializer(value); + result = Convert.ToString(serializer(value), CultureInfo.InvariantCulture); return true; } diff --git a/ObjectPrinting/PrintingConfigs/StringPropertyPrintingConfig.cs b/ObjectPrinting/PrintingConfigs/StringPropertyPrintingConfig.cs index fc410fd38..0134fce74 100644 --- a/ObjectPrinting/PrintingConfigs/StringPropertyPrintingConfig.cs +++ b/ObjectPrinting/PrintingConfigs/StringPropertyPrintingConfig.cs @@ -1,6 +1,5 @@ using System; using System.Linq.Expressions; -using System.Reflection; namespace ObjectPrinting.PrintingConfigs;