From 484d8fe5ec8ef1e84dbd0fd12376b88a8585542a Mon Sep 17 00:00:00 2001 From: Balashov2004 Date: Tue, 18 Nov 2025 17:30:18 +0500 Subject: [PATCH 01/13] first --- ObjectPrinting/PrintingConfig.cs | 63 ++++++++++++++----- ObjectPrinting/ReferenceEqualityComparer.cs | 13 ++++ .../Tests/ObjectPrinterAcceptanceTests.cs | 16 ++++- ObjectPrinting/Tests/Test.cs | 26 ++++++++ fluent-api.sln.DotSettings | 3 + 5 files changed, 102 insertions(+), 19 deletions(-) create mode 100644 ObjectPrinting/ReferenceEqualityComparer.cs create mode 100644 ObjectPrinting/Tests/Test.cs diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index a9e082117..222236e0d 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -1,41 +1,72 @@ using System; +using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Text; + namespace ObjectPrinting { public class PrintingConfig { public string PrintToString(TOwner obj) { - return PrintToString(obj, 0); + var visited = new HashSet(new ReferenceEqualityComparer()); + return PrintToString(obj, 0, visited); } - private string PrintToString(object obj, int nestingLevel) + private string PrintToString(object obj, int nestingLevel, HashSet visited) { - //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 type = obj.GetType(); + + if (IsFinalType(type)) + return obj.ToString() + Environment.NewLine; + + if (visited.Contains(obj)) + return $"" + Environment.NewLine; + + visited.Add(obj); + - var identation = new string('\t', nestingLevel + 1); var sb = new StringBuilder(); - var type = obj.GetType(); + var indent = new string('\t', nestingLevel); sb.AppendLine(type.Name); - foreach (var propertyInfo in type.GetProperties()) + + foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(p => p.GetIndexParameters().Length == 0)) + { + object value = property.GetValue(obj); + + sb.Append(indent + "\t" + property.Name + " = " + PrintToString(value, nestingLevel + 1, visited)); + } + + foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Instance)) { - sb.Append(identation + propertyInfo.Name + " = " + - PrintToString(propertyInfo.GetValue(obj), - nestingLevel + 1)); + object value = field.GetValue(obj); + sb.Append(indent + "\t" + field.Name + " = " + + PrintToString(value, nestingLevel + 1, visited)); } + + visited.Remove(obj); + + return sb.ToString(); } + + private static bool IsFinalType(Type type) + { + if (type.IsPrimitive) return true; + if (type.IsEnum) return true; + if (type == typeof(string)) return true; + if (type == typeof(decimal)) return true; + if (type == typeof(DateTime)) return true; + if (type == typeof(TimeSpan)) return true; + + return false; + } + } } \ No newline at end of file diff --git a/ObjectPrinting/ReferenceEqualityComparer.cs b/ObjectPrinting/ReferenceEqualityComparer.cs new file mode 100644 index 000000000..9c119b79c --- /dev/null +++ b/ObjectPrinting/ReferenceEqualityComparer.cs @@ -0,0 +1,13 @@ + +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + + +namespace ObjectPrinting; + +public class ReferenceEqualityComparer : IEqualityComparer +{ + public new bool Equals(object x, object y) => ReferenceEquals(x, y); + public int GetHashCode(object obj) => RuntimeHelpers.GetHashCode(obj); +} \ No newline at end of file diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs index 4c8b2445c..4bd59bc57 100644 --- a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs +++ b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs @@ -1,4 +1,5 @@ -using NUnit.Framework; +using System; +using NUnit.Framework; namespace ObjectPrinting.Tests { @@ -12,16 +13,25 @@ public void Demo() var printer = ObjectPrinter.For(); //1. Исключить из сериализации свойства определенного типа + .Excluding(); //2. Указать альтернативный способ сериализации для определенного типа + .Printing(i => $"Num is {i}"); //3. Для числовых типов указать культуру + .Printing(TypeNumber.withDot) //4. Настроить сериализацию конкретного свойства + .SelectMember(p => p.Name).Using(i => $"Age is {i}"); //5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств) + .SelectMember(p => p.Name).Trim(5); //6. Исключить из сериализации конкретного свойства + .Excluding(info => info.Age); string s1 = printer.PrintToString(person); - - //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию + + //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию + string s2 = person.PrintToString(); + //8. ...с конфигурированием + string s2 = person.PrintToString(info => info.Excluding(p => p.Age)); } } } \ No newline at end of file diff --git a/ObjectPrinting/Tests/Test.cs b/ObjectPrinting/Tests/Test.cs new file mode 100644 index 000000000..fd7148629 --- /dev/null +++ b/ObjectPrinting/Tests/Test.cs @@ -0,0 +1,26 @@ + +using System; +using NUnit.Framework; +using NUnit.Framework.Legacy; + +namespace ObjectPrinting.Tests; + +public class Test +{ + [Test] + public void PrintToString_ShouldHandleBasicObject() + { + var person = new Person + { + Name = "Alex", + Age = 30 + }; + + var result = ObjectPrinter.For().PrintToString(person); + + StringAssert.Contains("Person", result); + StringAssert.Contains("Name = Alex", result); + StringAssert.Contains("Age = 30", result); + StringAssert.Contains("Age = 30", result); + } +} \ No newline at end of file diff --git a/fluent-api.sln.DotSettings b/fluent-api.sln.DotSettings index 135b83ecb..53fe49b2f 100644 --- a/fluent-api.sln.DotSettings +++ b/fluent-api.sln.DotSettings @@ -1,6 +1,9 @@  <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb" /> + <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Types and namespaces"><ElementKinds><Kind Name="NAMESPACE" /><Kind Name="CLASS" /><Kind Name="STRUCT" /><Kind Name="ENUM" /><Kind Name="DELEGATE" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb_AaBb" /></Policy> + True True True Imported 10.10.2016 From 7f314ec160a5f5b79fca98dabff2770dfc4ec919 Mon Sep 17 00:00:00 2001 From: Balashov2004 Date: Wed, 19 Nov 2025 00:47:09 +0500 Subject: [PATCH 02/13] beta --- ObjectPrinting/MemberPrintingConfig.cs | 29 +++++ ObjectPrinting/ObjectPrinterExtensions.cs | 18 +++ ObjectPrinting/ObjectTraversal.cs | 113 ++++++++++++++++++ ObjectPrinting/PrintingConfig.cs | 93 ++++++-------- .../Tests/ObjectPrinterAcceptanceTests.cs | 20 ++-- ObjectPrinting/Tests/Person.cs | 5 +- ObjectPrinting/Tests/Test.cs | 26 ---- ObjectPrinting/TypeNumber.cs | 12 ++ ObjectPrinting/TypePrintingConfig.cs | 19 +++ .../interface/IPrintingConfigInternal.cs | 14 +++ 10 files changed, 253 insertions(+), 96 deletions(-) create mode 100644 ObjectPrinting/MemberPrintingConfig.cs create mode 100644 ObjectPrinting/ObjectPrinterExtensions.cs create mode 100644 ObjectPrinting/ObjectTraversal.cs delete mode 100644 ObjectPrinting/Tests/Test.cs create mode 100644 ObjectPrinting/TypeNumber.cs create mode 100644 ObjectPrinting/TypePrintingConfig.cs create mode 100644 ObjectPrinting/interface/IPrintingConfigInternal.cs diff --git a/ObjectPrinting/MemberPrintingConfig.cs b/ObjectPrinting/MemberPrintingConfig.cs new file mode 100644 index 000000000..cf6c9fd7a --- /dev/null +++ b/ObjectPrinting/MemberPrintingConfig.cs @@ -0,0 +1,29 @@ +using System; + +namespace ObjectPrinting; + +public class MemberPrintingConfig +{ + private readonly PrintingConfig config; + private readonly string memberName; + + internal MemberPrintingConfig(PrintingConfig config, string memberName) + { + this.config = config; + this.memberName = memberName; + } + public PrintingConfig Using(Func serializer) + { + config.PropertySerializers[memberName] = obj => serializer((TProp)obj); + return config; + } + + public PrintingConfig Trim(int maxLength) + { + if (typeof(TProp) != typeof(string)) + throw new InvalidOperationException("Trim только для строк"); + + config.TrimLengths[memberName] = maxLength; + return config; + } +} \ No newline at end of file diff --git a/ObjectPrinting/ObjectPrinterExtensions.cs b/ObjectPrinting/ObjectPrinterExtensions.cs new file mode 100644 index 000000000..56027a5e0 --- /dev/null +++ b/ObjectPrinting/ObjectPrinterExtensions.cs @@ -0,0 +1,18 @@ +using System; + +namespace ObjectPrinting; + +public static class ObjectPrinterExtensions +{ + public static string PrintToString(this T obj) + { + return new PrintingConfig().PrintToString(obj); + } + + public static string PrintToString( + this T obj, Func, PrintingConfig> config) + { + var cfg = config(new PrintingConfig()); + return cfg.PrintToString(obj); + } +} \ No newline at end of file diff --git a/ObjectPrinting/ObjectTraversal.cs b/ObjectPrinting/ObjectTraversal.cs new file mode 100644 index 000000000..c3e371832 --- /dev/null +++ b/ObjectPrinting/ObjectTraversal.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace ObjectPrinting; + +internal static class ObjectTraversal +{ + public static string Print(TOwner obj, PrintingConfig config) + { + var visited = new HashSet(new ReferenceEqualityComparer()); + return PrintInternal(obj, 0, config, visited); + } + + private static string PrintInternal( + object obj, + int level, + IPrintingConfigInternal config, + HashSet visited) + { + if (obj == null) + return "null\n"; + + var type = obj.GetType(); + + if (IsFinalType(type)) + return obj + "\n"; + + if (visited.Contains(obj)) + return $"\n"; + + visited.Add(obj); + + string indent = new string('\t', level); + var sb = new StringBuilder(); + + sb.AppendLine(type.Name); + + foreach (var property in type.GetProperties() + .Where(p => p.GetIndexParameters().Length == 0)) + { + if (config.ExcludedTypes.Contains(property.PropertyType)) continue; + if (config.ExcludedProperties.Contains(property.Name)) continue; + + var value = property.GetValue(obj); + string printed = PrintMember(property.Name, value, property.PropertyType, + config, level + 1, visited); + + sb.Append(indent + "\t" + printed); + } + + foreach (var field in type.GetFields()) + { + if (config.ExcludedTypes.Contains(field.FieldType)) continue; + if (config.ExcludedProperties.Contains(field.Name)) continue; + + var value = field.GetValue(obj); + string printed = PrintMember(field.Name, value, field.FieldType, + config, level + 1, visited); + + sb.Append(indent + "\t" + printed); + } + + return sb.ToString(); + } + + private static string PrintMember( + string memberName, + object value, + Type memberType, + IPrintingConfigInternal config, + int level, + HashSet visited) + { + if (config.PropertySerializers.TryGetValue(memberName, out var serializer)) + { + string val = serializer(value); + return memberName + " = " + val + "\n"; + } + + if (config.TypeSerializers.TryGetValue(memberType, out var typeSerializer)) + { + string val = typeSerializer(value); + return memberName + " = " + val + "\n"; + } + + if (config.TrimLengths.TryGetValue(memberName, out var maxLen) && + value is string s) + { + if (s.Length > maxLen) + s = s.Substring(0, maxLen); + + return memberName + " = " + s + "\n"; + } + + return memberName + " = " + + PrintInternal(value, level, config, visited); + } + + private static bool IsFinalType(Type type) + { + if (type.IsPrimitive) return true; + if (type == typeof(string)) return true; + if (type == typeof(decimal)) return true; + if (type == typeof(DateTime)) return true; + if (type == typeof(TimeSpan)) return true; + if (type.IsEnum) return true; + + return false; + } +} diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index 222236e0d..b26bc6201 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -1,72 +1,51 @@ using System; using System.Collections.Generic; -using System.Linq; +using System.Linq.Expressions; using System.Reflection; -using System.Text; +namespace ObjectPrinting; -namespace ObjectPrinting +public class PrintingConfig : IPrintingConfigInternal { - public class PrintingConfig - { - public string PrintToString(TOwner obj) - { - var visited = new HashSet(new ReferenceEqualityComparer()); - return PrintToString(obj, 0, visited); - } - - private string PrintToString(object obj, int nestingLevel, HashSet visited) - { - if (obj == null) - return "null" + Environment.NewLine; - - var type = obj.GetType(); - - if (IsFinalType(type)) - return obj.ToString() + Environment.NewLine; - - if (visited.Contains(obj)) - return $"" + Environment.NewLine; + public HashSet ExcludedTypes { get; } = new(); + public HashSet ExcludedProperties { get; } = new(); - visited.Add(obj); - + public Dictionary> TypeSerializers { get; } = new(); + public Dictionary> PropertySerializers { get; } = new(); + public Dictionary TrimLengths { get; } = new(); - var sb = new StringBuilder(); - var indent = new string('\t', nestingLevel); - sb.AppendLine(type.Name); - foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance) - .Where(p => p.GetIndexParameters().Length == 0)) - { - object value = property.GetValue(obj); - - sb.Append(indent + "\t" + property.Name + " = " + PrintToString(value, nestingLevel + 1, visited)); - } + public PrintingConfig Excluding() + { + ExcludedTypes.Add(typeof(TProp)); + return this; + } - foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Instance)) - { - object value = field.GetValue(obj); - sb.Append(indent + "\t" + field.Name + " = " + - PrintToString(value, nestingLevel + 1, visited)); - } - - visited.Remove(obj); + public PrintingConfig Excluding(Expression> selector) + { + var name = ((MemberExpression)selector.Body).Member.Name; + ExcludedProperties.Add(name); + return this; + } + + public TypePrintingConfig Printing() + => new TypePrintingConfig(this); - - return sb.ToString(); - } - - private static bool IsFinalType(Type type) - { - if (type.IsPrimitive) return true; - if (type.IsEnum) return true; - if (type == typeof(string)) return true; - if (type == typeof(decimal)) return true; - if (type == typeof(DateTime)) return true; - if (type == typeof(TimeSpan)) return true; + public PrintingConfig Printing(Func selector) + { + TypeSerializers[typeof(TProp)] = o => selector((TProp)o); + return this; + } - return false; - } + public MemberPrintingConfig SelectMember( + Expression> selector) + { + var name = ((MemberExpression)selector.Body).Member.Name; + return new MemberPrintingConfig(this, name); + } + public string PrintToString(TOwner obj) + { + return ObjectTraversal.Print(obj, this); } } \ No newline at end of file diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs index 4bd59bc57..f231d04b9 100644 --- a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs +++ b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs @@ -11,27 +11,25 @@ public void Demo() { var person = new Person { Name = "Alex", Age = 19 }; - var printer = ObjectPrinter.For(); + var printer = ObjectPrinter.For() //1. Исключить из сериализации свойства определенного типа - .Excluding(); + .Excluding() //2. Указать альтернативный способ сериализации для определенного типа - .Printing(i => $"Num is {i}"); + .Printing().Using(i => $"Num is {i}") //3. Для числовых типов указать культуру - .Printing(TypeNumber.withDot) + .Printing(TypeNumber.WithDot) //4. Настроить сериализацию конкретного свойства - .SelectMember(p => p.Name).Using(i => $"Age is {i}"); + .SelectMember(p => p.Name).Using(i => $"Age is {i}") //5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств) - .SelectMember(p => p.Name).Trim(5); + .SelectMember(p => p.Name).Trim(3) //6. Исключить из сериализации конкретного свойства .Excluding(info => info.Age); - string s1 = printer.PrintToString(person); - + printer.PrintToString(person); //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию - string s2 = person.PrintToString(); - + person.PrintToString(); //8. ...с конфигурированием - string s2 = person.PrintToString(info => info.Excluding(p => p.Age)); + person.PrintToString(info => info.Excluding(p => p.Age)); } } } \ No newline at end of file diff --git a/ObjectPrinting/Tests/Person.cs b/ObjectPrinting/Tests/Person.cs index f95559554..f137f33aa 100644 --- a/ObjectPrinting/Tests/Person.cs +++ b/ObjectPrinting/Tests/Person.cs @@ -4,9 +4,10 @@ namespace ObjectPrinting.Tests { public class Person { - public Guid Id { get; set; } public string Name { get; set; } - public double Height { get; set; } public int Age { get; set; } + public double Score { get; set; } + public Guid Id { get; set; } + public Person Friend { get; set; } } } \ No newline at end of file diff --git a/ObjectPrinting/Tests/Test.cs b/ObjectPrinting/Tests/Test.cs deleted file mode 100644 index fd7148629..000000000 --- a/ObjectPrinting/Tests/Test.cs +++ /dev/null @@ -1,26 +0,0 @@ - -using System; -using NUnit.Framework; -using NUnit.Framework.Legacy; - -namespace ObjectPrinting.Tests; - -public class Test -{ - [Test] - public void PrintToString_ShouldHandleBasicObject() - { - var person = new Person - { - Name = "Alex", - Age = 30 - }; - - var result = ObjectPrinter.For().PrintToString(person); - - StringAssert.Contains("Person", result); - StringAssert.Contains("Name = Alex", result); - StringAssert.Contains("Age = 30", result); - StringAssert.Contains("Age = 30", result); - } -} \ No newline at end of file diff --git a/ObjectPrinting/TypeNumber.cs b/ObjectPrinting/TypeNumber.cs new file mode 100644 index 000000000..886c8e9af --- /dev/null +++ b/ObjectPrinting/TypeNumber.cs @@ -0,0 +1,12 @@ +using System; +using System.Globalization; + +namespace ObjectPrinting; + +public class TypeNumber +{ + public static Func WithDot => + d => d.ToString(CultureInfo.InvariantCulture); + public static Func WithComma => + d => d.ToString(new CultureInfo("ru-RU")); +} \ No newline at end of file diff --git a/ObjectPrinting/TypePrintingConfig.cs b/ObjectPrinting/TypePrintingConfig.cs new file mode 100644 index 000000000..7bf3d0e5a --- /dev/null +++ b/ObjectPrinting/TypePrintingConfig.cs @@ -0,0 +1,19 @@ +using System; + +namespace ObjectPrinting; + +public class TypePrintingConfig +{ + private readonly PrintingConfig config; + + internal TypePrintingConfig(PrintingConfig config) + { + this.config = config; + } + + public PrintingConfig Using(Func serializer) + { + config.TypeSerializers[typeof(TProp)] = obj => serializer((TProp)obj); + return config; + } +} \ No newline at end of file diff --git a/ObjectPrinting/interface/IPrintingConfigInternal.cs b/ObjectPrinting/interface/IPrintingConfigInternal.cs new file mode 100644 index 000000000..222526f35 --- /dev/null +++ b/ObjectPrinting/interface/IPrintingConfigInternal.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; + +namespace ObjectPrinting; + +public interface IPrintingConfigInternal +{ + HashSet ExcludedTypes { get; } + HashSet ExcludedProperties { get; } + + Dictionary> TypeSerializers { get; } + Dictionary> PropertySerializers { get; } + Dictionary TrimLengths { get; } +} From 2e0bbd6530e31bda5e3388ec75f358e0b0554544 Mon Sep 17 00:00:00 2001 From: Balashov2004 Date: Wed, 19 Nov 2025 23:45:01 +0500 Subject: [PATCH 03/13] beta2 --- ObjectPrinting/ObjectTraversal.cs | 244 +++++++++++------- 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 - ObjectPrinting/Tests/Container.cs | 10 + .../Tests/ObjectPrinterAcceptanceTests.cs | 70 ++--- ObjectPrinting/Tests/Person.cs | 2 +- ObjectPrinting/Tests/Tests.cs | 146 +++++++++++ 12 files changed, 345 insertions(+), 311 deletions(-) delete mode 100644 ObjectPrinting/Solved/Tests/ObjectPrinterAcceptanceTests.cs delete mode 100644 ObjectPrinting/Solved/Tests/Person.cs create mode 100644 ObjectPrinting/Tests/Container.cs create mode 100644 ObjectPrinting/Tests/Tests.cs diff --git a/ObjectPrinting/ObjectTraversal.cs b/ObjectPrinting/ObjectTraversal.cs index c3e371832..469133bd3 100644 --- a/ObjectPrinting/ObjectTraversal.cs +++ b/ObjectPrinting/ObjectTraversal.cs @@ -1,113 +1,175 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; -namespace ObjectPrinting; - -internal static class ObjectTraversal +namespace ObjectPrinting { - public static string Print(TOwner obj, PrintingConfig config) - { - var visited = new HashSet(new ReferenceEqualityComparer()); - return PrintInternal(obj, 0, config, visited); - } - - private static string PrintInternal( - object obj, - int level, - IPrintingConfigInternal config, - HashSet visited) + public static class ObjectTraversal { - if (obj == null) - return "null\n"; - - var type = obj.GetType(); - - if (IsFinalType(type)) - return obj + "\n"; - - if (visited.Contains(obj)) - return $"\n"; - - visited.Add(obj); - - string indent = new string('\t', level); - var sb = new StringBuilder(); - - sb.AppendLine(type.Name); - - foreach (var property in type.GetProperties() - .Where(p => p.GetIndexParameters().Length == 0)) + public static string Print(TOwner obj, PrintingConfig config) { - if (config.ExcludedTypes.Contains(property.PropertyType)) continue; - if (config.ExcludedProperties.Contains(property.Name)) continue; - - var value = property.GetValue(obj); - string printed = PrintMember(property.Name, value, property.PropertyType, - config, level + 1, visited); - - sb.Append(indent + "\t" + printed); + var visited = new HashSet(new ReferenceEqualityComparer()); + return PrintInternal(obj, 0, config, visited); } - - foreach (var field in type.GetFields()) - { - if (config.ExcludedTypes.Contains(field.FieldType)) continue; - if (config.ExcludedProperties.Contains(field.Name)) continue; - - var value = field.GetValue(obj); - string printed = PrintMember(field.Name, value, field.FieldType, - config, level + 1, visited); - sb.Append(indent + "\t" + printed); + private static string PrintInternal( + object obj, + int level, + IPrintingConfigInternal config, + HashSet visited) + { + if (obj == null) + return "null\n"; + + var type = obj.GetType(); + + if (IsFinalType(type)) + return obj + "\n"; + + if (!type.IsValueType) + { + if (visited.Contains(obj)) + return $"\n"; + + visited.Add(obj); + } + + if (obj is IEnumerable numerable && obj is not string) + { + return PrintEnumerable(numerable, type, level, config, visited); + } + + string indentObj = new string('\t', level); + var sbObj = new StringBuilder(); + + sbObj.AppendLine(type.Name); + + + foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(p => p.GetIndexParameters().Length == 0)) + { + if (config.ExcludedTypes.Contains(property.PropertyType)) continue; + if (config.ExcludedProperties.Contains(property.Name)) continue; + + var value = property.GetValue(obj); + string printed = PrintMember(property.Name, value, property.PropertyType, + config, level + 1, visited); + + sbObj.Append(indentObj + "\t" + printed); + } + + foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Instance)) + { + if (config.ExcludedTypes.Contains(field.FieldType)) continue; + if (config.ExcludedProperties.Contains(field.Name)) continue; + + var value = field.GetValue(obj); + string printed = PrintMember(field.Name, value, field.FieldType, + config, level + 1, visited); + + sbObj.Append(indentObj + "\t" + printed); + } + + return sbObj.ToString(); } - - return sb.ToString(); - } - private static string PrintMember( - string memberName, - object value, - Type memberType, - IPrintingConfigInternal config, - int level, - HashSet visited) - { - if (config.PropertySerializers.TryGetValue(memberName, out var serializer)) + private static string PrintMember( + string memberName, + object value, + Type memberType, + IPrintingConfigInternal config, + int level, + HashSet visited) { - string val = serializer(value); - return memberName + " = " + val + "\n"; + if (config.PropertySerializers.TryGetValue(memberName, out var serializer)) + { + string val = serializer(value); + return memberName + " = " + val + "\n"; + } + + if (config.TypeSerializers.TryGetValue(memberType, out var typeSerializer)) + { + string val = typeSerializer(value); + return memberName + " = " + val + "\n"; + } + + if (config.TrimLengths.TryGetValue(memberName, out var maxLen) && value is string s) + { + if (s.Length > maxLen) + s = s.Substring(0, maxLen); + + return memberName + " = " + s + "\n"; + } + + if (value == null) + return memberName + " = null\n"; + + if (IsFinalType(memberType)) + return memberName + " = " + value + "\n"; + + return memberName + " = " + PrintInternal(value, level, config, visited); } - if (config.TypeSerializers.TryGetValue(memberType, out var typeSerializer)) + private static string PrintEnumerable( + IEnumerable enumerable, + Type type, + int level, + IPrintingConfigInternal config, + HashSet visited) { - string val = typeSerializer(value); - return memberName + " = " + val + "\n"; + var indent = new string('\t', level); + var sb = new StringBuilder(); + + sb.AppendLine(type.Name); + + if (enumerable is IDictionary nonGenericDict) + { + foreach (DictionaryEntry entry in nonGenericDict) + { + sb.Append(indent + "\t"); + sb.Append($"[{entry.Key}] = {PrintInternal(entry.Value, level + 1, config, visited)}"); + } + } + else + { + int i = 0; + foreach (var item in enumerable) + { + sb.Append(indent + "\t"); + + var itemType = item.GetType(); + if (itemType.IsGenericType && + itemType.GetGenericTypeDefinition() == typeof(KeyValuePair<,>)) + { + dynamic kv = item; + sb.Append($"[{kv.Key}] = {PrintInternal(kv.Value, level + 1, config, visited)}"); + } + else + { + sb.Append($"[{i}] = {PrintInternal(item, level + 1, config, visited)}"); + } + + i++; + } + } + + return sb.ToString(); + } - - if (config.TrimLengths.TryGetValue(memberName, out var maxLen) && - value is string s) - { - if (s.Length > maxLen) - s = s.Substring(0, maxLen); - return memberName + " = " + s + "\n"; + private static bool IsFinalType(Type type) + { + if (type.IsPrimitive) return true; + if (type == typeof(string)) return true; + if (type == typeof(decimal)) return true; + if (type == typeof(DateTime)) return true; + if (type == typeof(TimeSpan)) return true; + if (type.IsEnum) return true; + + return false; } - - return memberName + " = " + - PrintInternal(value, level, config, visited); - } - - private static bool IsFinalType(Type type) - { - if (type.IsPrimitive) return true; - if (type == typeof(string)) return true; - if (type == typeof(decimal)) return true; - if (type == typeof(DateTime)) return true; - if (type == typeof(TimeSpan)) return true; - if (type.IsEnum) return true; - - return false; } } diff --git a/ObjectPrinting/Solved/ObjectExtensions.cs b/ObjectPrinting/Solved/ObjectExtensions.cs index b0c94553c..e69de29bb 100644 --- a/ObjectPrinting/Solved/ObjectExtensions.cs +++ b/ObjectPrinting/Solved/ObjectExtensions.cs @@ -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 index 540ee769c..e69de29bb 100644 --- a/ObjectPrinting/Solved/ObjectPrinter.cs +++ b/ObjectPrinting/Solved/ObjectPrinter.cs @@ -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 index 0ec5aeb2b..e69de29bb 100644 --- a/ObjectPrinting/Solved/PrintingConfig.cs +++ b/ObjectPrinting/Solved/PrintingConfig.cs @@ -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 index a509697d1..e69de29bb 100644 --- a/ObjectPrinting/Solved/PropertyPrintingConfig.cs +++ b/ObjectPrinting/Solved/PropertyPrintingConfig.cs @@ -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 index dd3922394..e69de29bb 100644 --- a/ObjectPrinting/Solved/PropertyPrintingConfigExtensions.cs +++ b/ObjectPrinting/Solved/PropertyPrintingConfigExtensions.cs @@ -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/ObjectPrinting/Tests/Container.cs b/ObjectPrinting/Tests/Container.cs new file mode 100644 index 000000000..048c9adca --- /dev/null +++ b/ObjectPrinting/Tests/Container.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace ObjectPrinting.Tests; + +public class Container +{ + public int[] Array { get; set; } + public List List { get; set; } + public Dictionary Dict { get; set; } +} \ No newline at end of file diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs index f231d04b9..1c6626165 100644 --- a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs +++ b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs @@ -1,35 +1,35 @@ -using System; -using NUnit.Framework; - -namespace ObjectPrinting.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 => $"Num is {i}") - //3. Для числовых типов указать культуру - .Printing(TypeNumber.WithDot) - //4. Настроить сериализацию конкретного свойства - .SelectMember(p => p.Name).Using(i => $"Age is {i}") - //5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств) - .SelectMember(p => p.Name).Trim(3) - //6. Исключить из сериализации конкретного свойства - .Excluding(info => info.Age); - - printer.PrintToString(person); - //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию - person.PrintToString(); - //8. ...с конфигурированием - person.PrintToString(info => info.Excluding(p => p.Age)); - } - } -} \ No newline at end of file +// using System; +// using NUnit.Framework; +// +// namespace ObjectPrinting.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 => $"Num is {i}") +// //3. Для числовых типов указать культуру +// .Printing(TypeNumber.WithDot) +// //4. Настроить сериализацию конкретного свойства +// .SelectMember(p => p.Name).Using(i => $"Age is {i}") +// //5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств) +// .SelectMember(p => p.Name).Trim(3) +// //6. Исключить из сериализации конкретного свойства +// .Excluding(info => info.Age); +// +// printer.PrintToString(person); +// //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию +// person.PrintToString(); +// //8. ...с конфигурированием +// person.PrintToString(info => info.Excluding(p => p.Age)); +// } +// } +// } \ No newline at end of file diff --git a/ObjectPrinting/Tests/Person.cs b/ObjectPrinting/Tests/Person.cs index f137f33aa..c7164809f 100644 --- a/ObjectPrinting/Tests/Person.cs +++ b/ObjectPrinting/Tests/Person.cs @@ -4,10 +4,10 @@ namespace ObjectPrinting.Tests { public class Person { + public string FirstName; public string Name { get; set; } public int Age { get; set; } public double Score { get; set; } public Guid Id { get; set; } - public Person Friend { get; set; } } } \ No newline at end of file diff --git a/ObjectPrinting/Tests/Tests.cs b/ObjectPrinting/Tests/Tests.cs new file mode 100644 index 000000000..667330708 --- /dev/null +++ b/ObjectPrinting/Tests/Tests.cs @@ -0,0 +1,146 @@ + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using NUnit.Framework.Legacy; + +namespace ObjectPrinting.Tests; + +public class Tests +{ + [Test] + public void ExcludingTypeTest() + { + var person = new Person { Name = "Alex", Age = 25, Id = Guid.NewGuid() }; + var printer = ObjectPrinter.For() + .Excluding() + .Excluding(); + string printed = printer.PrintToString(person); + Assert.That(printed, Does.Not.Contain("Id")); + Assert.That(printed, Does.Not.Contain("Age")); + Assert.That(printed, Contains.Substring("Name")); + } + + [Test] + public void ExcludingPropertyTest() + { + var person = new Person { Name = "Alex", Age = 25, Id = Guid.NewGuid() }; + var printer = ObjectPrinter.For() + .Excluding(p => p.Age); + string printed = printer.PrintToString(person); + Assert.That(printed, Does.Not.Contain("Age")); + Assert.That(printed, Contains.Substring("Name")); + } + + [Test] + public void CustomTypeSerializationTest() + { + var person = new Person { Name = "Alex", Age = 25, Id = Guid.NewGuid() }; + var printer = ObjectPrinter.For() + .Printing(i => $"Number is {i}"); + string printed = printer.PrintToString(person); + Assert.That(printed, Contains.Substring("Number is 25")); + } + + [Test] + public void TrimTest() + { + var person = new Person { Name = "Alex", Age = 25, Id = Guid.NewGuid() }; + var printer = ObjectPrinter.For() + .SelectMember(p => p.Name).Trim(3); + string printed = printer.PrintToString(person); + Assert.That(printed, Contains.Substring("Name = Ale")); + } + + [Test] + public void CustomDoubleTest() + { + var person = new Person { Name = "Alex", Score = 12.5 }; + var printer1 = ObjectPrinter.For() + .Printing(TypeNumber.WithDot); + string printed1 = printer1.PrintToString(person);; + Assert.That(printed1, Contains.Substring("12.5")); + + var printer2 = ObjectPrinter.For() + .Printing(TypeNumber.WithComma); + string printed2 = printer2.PrintToString(person); + Assert.That(printed2, Contains.Substring("12,5")); + } + + [Test] + public void ExtensionWithConfig() + { + var person = new Person { Name = "Alex", Age = 25 }; + + string printed = person.PrintToString(cfg => cfg.Excluding(p => p.Age)); + + Assert.That(printed, Does.Not.Contain("Age")); + Assert.That(printed, Contains.Substring("Alex")); + } + + [Test] + public void SerializeFieldsTest() + { + var person = new Person { FirstName = "Ivanov", Name = "Alex", Age = 25 }; + var printer = ObjectPrinter.For(); + string printed = printer.PrintToString(person); + + Assert.That(printed, Contains.Substring("FirstName")); + Assert.That(printed, Contains.Substring("Ivanov")); + } + + [Test] + public void ArraySerializeTest() + { + var obj = new Container + { + Array = new[] { 1, 2, 3 } + }; + + var printer = ObjectPrinter.For(); + string result = printer.PrintToString(obj); + + Assert.That(result, Contains.Substring("Array")); + Assert.That(result, Contains.Substring("[0] = 1")); + Assert.That(result, Contains.Substring("[1] = 2")); + Assert.That(result, Contains.Substring("[2] = 3")); + } + + [Test] + public void ListSerializeTest() + { + var obj = new Container + { + List = new List { "Alex", "Ivan" } + }; + + var printer = ObjectPrinter.For(); + string result = printer.PrintToString(obj); + + Assert.That(result, Contains.Substring("List")); + Assert.That(result, Contains.Substring("[0] = Alex")); + Assert.That(result, Contains.Substring("[1] = Ivan")); + } + + [Test] + public void DictionarySerializationTest() + { + var obj = new Container + { + Dict = new Dictionary + { + ["Alex"] = 19, + ["Ivan"] = 21 + } + }; + + var printer = ObjectPrinter.For(); + string result = printer.PrintToString(obj); + + Assert.That(result, Contains.Substring("Dict")); + Assert.That(result, Contains.Substring("[Alex] = 19")); + Assert.That(result, Contains.Substring("[Ivan] = 21")); + } + + +} \ No newline at end of file From a42948b9dcef2737105abd00ed8386e7b873e271 Mon Sep 17 00:00:00 2001 From: Balashov2004 Date: Fri, 21 Nov 2025 17:20:39 +0500 Subject: [PATCH 04/13] fix some 2 --- .../IPrintingConfigInternal.cs | 3 +- ObjectPrinting/MemberPrintingConfig.cs | 6 +- ObjectPrinting/ObjectSerializer.cs | 178 ++++++++++++++++++ ObjectPrinting/ObjectTraversal.cs | 175 ----------------- ObjectPrinting/PrintingConfig.cs | 15 +- .../Tests/{Tests.cs => ObjectPrinterTests.cs} | 23 ++- ObjectPrinting/TypePrintingConfig.cs | 19 -- 7 files changed, 204 insertions(+), 215 deletions(-) rename ObjectPrinting/{interface => Interface}/IPrintingConfigInternal.cs (81%) create mode 100644 ObjectPrinting/ObjectSerializer.cs delete mode 100644 ObjectPrinting/ObjectTraversal.cs rename ObjectPrinting/Tests/{Tests.cs => ObjectPrinterTests.cs} (86%) delete mode 100644 ObjectPrinting/TypePrintingConfig.cs diff --git a/ObjectPrinting/interface/IPrintingConfigInternal.cs b/ObjectPrinting/Interface/IPrintingConfigInternal.cs similarity index 81% rename from ObjectPrinting/interface/IPrintingConfigInternal.cs rename to ObjectPrinting/Interface/IPrintingConfigInternal.cs index 222526f35..4e0a081d2 100644 --- a/ObjectPrinting/interface/IPrintingConfigInternal.cs +++ b/ObjectPrinting/Interface/IPrintingConfigInternal.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -namespace ObjectPrinting; +namespace ObjectPrinting.Interface; public interface IPrintingConfigInternal { @@ -11,4 +11,5 @@ public interface IPrintingConfigInternal Dictionary> TypeSerializers { get; } Dictionary> PropertySerializers { get; } Dictionary TrimLengths { get; } + public int? GlobalStringTrimLength { get; } } diff --git a/ObjectPrinting/MemberPrintingConfig.cs b/ObjectPrinting/MemberPrintingConfig.cs index cf6c9fd7a..411cd25d7 100644 --- a/ObjectPrinting/MemberPrintingConfig.cs +++ b/ObjectPrinting/MemberPrintingConfig.cs @@ -12,17 +12,15 @@ internal MemberPrintingConfig(PrintingConfig config, string memberName) this.config = config; this.memberName = memberName; } + public PrintingConfig Using(Func serializer) { config.PropertySerializers[memberName] = obj => serializer((TProp)obj); return config; } - + public PrintingConfig Trim(int maxLength) { - if (typeof(TProp) != typeof(string)) - throw new InvalidOperationException("Trim только для строк"); - config.TrimLengths[memberName] = maxLength; return config; } diff --git a/ObjectPrinting/ObjectSerializer.cs b/ObjectPrinting/ObjectSerializer.cs new file mode 100644 index 000000000..0bdf0222c --- /dev/null +++ b/ObjectPrinting/ObjectSerializer.cs @@ -0,0 +1,178 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using ObjectPrinting.Interface; + +namespace ObjectPrinting; + +public class ObjectSerializer +{ + public string Print(TOwner obj, PrintingConfig config) + { + var visited = new HashSet(new ReferenceEqualityComparer()); + return PrintInternal(obj, 0, config, visited); + } + + private string PrintInternal( + object obj, + int level, + IPrintingConfigInternal config, + HashSet visited) + { + if (obj == null) + return "null" + Environment.NewLine; + + var type = obj.GetType(); + + if (IsSimpleType(type)) + return obj + Environment.NewLine; + + if (!type.IsValueType) + { + if (!visited.Add(obj)) + return $"" + Environment.NewLine; + } + + if (obj is IEnumerable numerable) + { + return PrintEnumerable(numerable, type, level, config, visited); + } + + var indentObj = new string('\t', level); + var sbObj = new StringBuilder(); + + sbObj.AppendLine(type.Name); + + var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(p => p.GetIndexParameters().Length == 0); + foreach (var property in properties) + { + if (config.ExcludedTypes.Contains(property.PropertyType)) continue; + if (config.ExcludedProperties.Contains(property.Name)) continue; + + var value = property.GetValue(obj); + var printed = PrintMember(property.Name, value, property.PropertyType, + config, level + 1, visited); + + sbObj.Append(indentObj + "\t" + printed); + } + + var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance); + foreach (var field in fields) + { + if (config.ExcludedTypes.Contains(field.FieldType)) continue; + if (config.ExcludedProperties.Contains(field.Name)) continue; + + var value = field.GetValue(obj); + var printed = PrintMember(field.Name, value, field.FieldType, + config, level + 1, visited); + + sbObj.Append(indentObj + "\t" + printed); + } + + return sbObj.ToString(); + } + + private string PrintMember( + string memberName, + object value, + Type memberType, + IPrintingConfigInternal config, + int level, + HashSet visited) + { + if (config.PropertySerializers.TryGetValue(memberName, out var serializer)) + { + var val = serializer(value); + return memberName + " = " + val + Environment.NewLine; + } + + if (config.TypeSerializers.TryGetValue(memberType, out var typeSerializer)) + { + var val = typeSerializer(value); + return memberName + " = " + val + Environment.NewLine; + } + + if (value is string s) + { + int? maxLen = null; + + if (config.TrimLengths.TryGetValue(memberName, out var propLen)) + maxLen = propLen; + + else if (config.GlobalStringTrimLength.HasValue) + maxLen = config.GlobalStringTrimLength.Value; + + if (maxLen.HasValue && s.Length > maxLen.Value) + s = s.Substring(0, maxLen.Value); + + return memberName + " = " + s + Environment.NewLine; + } + + if (IsSimpleType(memberType)) + return memberName + " = " + value + Environment.NewLine; + + return memberName + " = " + PrintInternal(value, level, config, visited); + } + + private string PrintEnumerable( + IEnumerable enumerable, + Type type, + int level, + IPrintingConfigInternal config, + HashSet visited) + { + var indent = new string('\t', level); + var sb = new StringBuilder(); + + sb.AppendLine(type.Name); + + if (enumerable is IDictionary nonGenericDict) + { + foreach (DictionaryEntry entry in nonGenericDict) + { + sb.Append(indent + "\t"); + sb.Append($"[{entry.Key}] = {PrintInternal(entry.Value, level + 1, config, visited)}"); + } + } + else + { + var i = 0; + foreach (var item in enumerable) + { + sb.Append(indent + "\t"); + + var itemType = item.GetType(); + if (itemType.IsGenericType && + itemType.GetGenericTypeDefinition() == typeof(KeyValuePair<,>)) + { + dynamic kv = item; + sb.Append($"[{kv.Key}] = {PrintInternal(kv.Value, level + 1, config, visited)}"); + } + else + { + sb.Append($"[{i}] = {PrintInternal(item, level + 1, config, visited)}"); + } + + i++; + } + } + + return sb.ToString(); + } + + private bool IsSimpleType(Type type) + { + if (type.IsPrimitive) return true; + if (type == typeof(string)) return true; + if (type == typeof(decimal)) return true; + if (type == typeof(DateTime)) return true; + if (type == typeof(TimeSpan)) return true; + if (type.IsEnum) return true; + + return false; + } +} \ No newline at end of file diff --git a/ObjectPrinting/ObjectTraversal.cs b/ObjectPrinting/ObjectTraversal.cs deleted file mode 100644 index 469133bd3..000000000 --- a/ObjectPrinting/ObjectTraversal.cs +++ /dev/null @@ -1,175 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; - -namespace ObjectPrinting -{ - public static class ObjectTraversal - { - public static string Print(TOwner obj, PrintingConfig config) - { - var visited = new HashSet(new ReferenceEqualityComparer()); - return PrintInternal(obj, 0, config, visited); - } - - private static string PrintInternal( - object obj, - int level, - IPrintingConfigInternal config, - HashSet visited) - { - if (obj == null) - return "null\n"; - - var type = obj.GetType(); - - if (IsFinalType(type)) - return obj + "\n"; - - if (!type.IsValueType) - { - if (visited.Contains(obj)) - return $"\n"; - - visited.Add(obj); - } - - if (obj is IEnumerable numerable && obj is not string) - { - return PrintEnumerable(numerable, type, level, config, visited); - } - - string indentObj = new string('\t', level); - var sbObj = new StringBuilder(); - - sbObj.AppendLine(type.Name); - - - foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance) - .Where(p => p.GetIndexParameters().Length == 0)) - { - if (config.ExcludedTypes.Contains(property.PropertyType)) continue; - if (config.ExcludedProperties.Contains(property.Name)) continue; - - var value = property.GetValue(obj); - string printed = PrintMember(property.Name, value, property.PropertyType, - config, level + 1, visited); - - sbObj.Append(indentObj + "\t" + printed); - } - - foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Instance)) - { - if (config.ExcludedTypes.Contains(field.FieldType)) continue; - if (config.ExcludedProperties.Contains(field.Name)) continue; - - var value = field.GetValue(obj); - string printed = PrintMember(field.Name, value, field.FieldType, - config, level + 1, visited); - - sbObj.Append(indentObj + "\t" + printed); - } - - return sbObj.ToString(); - } - - private static string PrintMember( - string memberName, - object value, - Type memberType, - IPrintingConfigInternal config, - int level, - HashSet visited) - { - if (config.PropertySerializers.TryGetValue(memberName, out var serializer)) - { - string val = serializer(value); - return memberName + " = " + val + "\n"; - } - - if (config.TypeSerializers.TryGetValue(memberType, out var typeSerializer)) - { - string val = typeSerializer(value); - return memberName + " = " + val + "\n"; - } - - if (config.TrimLengths.TryGetValue(memberName, out var maxLen) && value is string s) - { - if (s.Length > maxLen) - s = s.Substring(0, maxLen); - - return memberName + " = " + s + "\n"; - } - - if (value == null) - return memberName + " = null\n"; - - if (IsFinalType(memberType)) - return memberName + " = " + value + "\n"; - - return memberName + " = " + PrintInternal(value, level, config, visited); - } - - private static string PrintEnumerable( - IEnumerable enumerable, - Type type, - int level, - IPrintingConfigInternal config, - HashSet visited) - { - var indent = new string('\t', level); - var sb = new StringBuilder(); - - sb.AppendLine(type.Name); - - if (enumerable is IDictionary nonGenericDict) - { - foreach (DictionaryEntry entry in nonGenericDict) - { - sb.Append(indent + "\t"); - sb.Append($"[{entry.Key}] = {PrintInternal(entry.Value, level + 1, config, visited)}"); - } - } - else - { - int i = 0; - foreach (var item in enumerable) - { - sb.Append(indent + "\t"); - - var itemType = item.GetType(); - if (itemType.IsGenericType && - itemType.GetGenericTypeDefinition() == typeof(KeyValuePair<,>)) - { - dynamic kv = item; - sb.Append($"[{kv.Key}] = {PrintInternal(kv.Value, level + 1, config, visited)}"); - } - else - { - sb.Append($"[{i}] = {PrintInternal(item, level + 1, config, visited)}"); - } - - i++; - } - } - - return sb.ToString(); - - } - - private static bool IsFinalType(Type type) - { - if (type.IsPrimitive) return true; - if (type == typeof(string)) return true; - if (type == typeof(decimal)) return true; - if (type == typeof(DateTime)) return true; - if (type == typeof(TimeSpan)) return true; - if (type.IsEnum) return true; - - return false; - } - } -} diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index b26bc6201..6038043ea 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq.Expressions; using System.Reflection; +using ObjectPrinting.Interface; namespace ObjectPrinting; @@ -13,6 +14,15 @@ public class PrintingConfig : IPrintingConfigInternal public Dictionary> TypeSerializers { get; } = new(); public Dictionary> PropertySerializers { get; } = new(); public Dictionary TrimLengths { get; } = new(); + public int? GlobalStringTrimLength { get; private set; } + + private ObjectSerializer objectSerializer = new ObjectSerializer(); + + public PrintingConfig TrimStringsToLength(int maxLength) + { + GlobalStringTrimLength = maxLength; + return this; + } public PrintingConfig Excluding() @@ -27,9 +37,6 @@ public PrintingConfig Excluding(Expression> s ExcludedProperties.Add(name); return this; } - - public TypePrintingConfig Printing() - => new TypePrintingConfig(this); public PrintingConfig Printing(Func selector) { @@ -46,6 +53,6 @@ public MemberPrintingConfig SelectMember( public string PrintToString(TOwner obj) { - return ObjectTraversal.Print(obj, this); + return objectSerializer.Print(obj, this); } } \ No newline at end of file diff --git a/ObjectPrinting/Tests/Tests.cs b/ObjectPrinting/Tests/ObjectPrinterTests.cs similarity index 86% rename from ObjectPrinting/Tests/Tests.cs rename to ObjectPrinting/Tests/ObjectPrinterTests.cs index 667330708..25e57822e 100644 --- a/ObjectPrinting/Tests/Tests.cs +++ b/ObjectPrinting/Tests/ObjectPrinterTests.cs @@ -2,11 +2,10 @@ using System; using System.Collections.Generic; using NUnit.Framework; -using NUnit.Framework.Legacy; namespace ObjectPrinting.Tests; -public class Tests +public class ObjectPrinterTests { [Test] public void ExcludingTypeTest() @@ -15,7 +14,7 @@ public void ExcludingTypeTest() var printer = ObjectPrinter.For() .Excluding() .Excluding(); - string printed = printer.PrintToString(person); + var printed = printer.PrintToString(person); Assert.That(printed, Does.Not.Contain("Id")); Assert.That(printed, Does.Not.Contain("Age")); Assert.That(printed, Contains.Substring("Name")); @@ -27,7 +26,7 @@ public void ExcludingPropertyTest() var person = new Person { Name = "Alex", Age = 25, Id = Guid.NewGuid() }; var printer = ObjectPrinter.For() .Excluding(p => p.Age); - string printed = printer.PrintToString(person); + var printed = printer.PrintToString(person); Assert.That(printed, Does.Not.Contain("Age")); Assert.That(printed, Contains.Substring("Name")); } @@ -38,7 +37,7 @@ public void CustomTypeSerializationTest() var person = new Person { Name = "Alex", Age = 25, Id = Guid.NewGuid() }; var printer = ObjectPrinter.For() .Printing(i => $"Number is {i}"); - string printed = printer.PrintToString(person); + var printed = printer.PrintToString(person); Assert.That(printed, Contains.Substring("Number is 25")); } @@ -48,7 +47,7 @@ public void TrimTest() var person = new Person { Name = "Alex", Age = 25, Id = Guid.NewGuid() }; var printer = ObjectPrinter.For() .SelectMember(p => p.Name).Trim(3); - string printed = printer.PrintToString(person); + var printed = printer.PrintToString(person); Assert.That(printed, Contains.Substring("Name = Ale")); } @@ -63,7 +62,7 @@ public void CustomDoubleTest() var printer2 = ObjectPrinter.For() .Printing(TypeNumber.WithComma); - string printed2 = printer2.PrintToString(person); + var printed2 = printer2.PrintToString(person); Assert.That(printed2, Contains.Substring("12,5")); } @@ -72,7 +71,7 @@ public void ExtensionWithConfig() { var person = new Person { Name = "Alex", Age = 25 }; - string printed = person.PrintToString(cfg => cfg.Excluding(p => p.Age)); + var printed = person.PrintToString(cfg => cfg.Excluding(p => p.Age)); Assert.That(printed, Does.Not.Contain("Age")); Assert.That(printed, Contains.Substring("Alex")); @@ -83,7 +82,7 @@ public void SerializeFieldsTest() { var person = new Person { FirstName = "Ivanov", Name = "Alex", Age = 25 }; var printer = ObjectPrinter.For(); - string printed = printer.PrintToString(person); + var printed = printer.PrintToString(person); Assert.That(printed, Contains.Substring("FirstName")); Assert.That(printed, Contains.Substring("Ivanov")); @@ -98,7 +97,7 @@ public void ArraySerializeTest() }; var printer = ObjectPrinter.For(); - string result = printer.PrintToString(obj); + var result = printer.PrintToString(obj); Assert.That(result, Contains.Substring("Array")); Assert.That(result, Contains.Substring("[0] = 1")); @@ -115,7 +114,7 @@ public void ListSerializeTest() }; var printer = ObjectPrinter.For(); - string result = printer.PrintToString(obj); + var result = printer.PrintToString(obj); Assert.That(result, Contains.Substring("List")); Assert.That(result, Contains.Substring("[0] = Alex")); @@ -135,7 +134,7 @@ public void DictionarySerializationTest() }; var printer = ObjectPrinter.For(); - string result = printer.PrintToString(obj); + var result = printer.PrintToString(obj); Assert.That(result, Contains.Substring("Dict")); Assert.That(result, Contains.Substring("[Alex] = 19")); diff --git a/ObjectPrinting/TypePrintingConfig.cs b/ObjectPrinting/TypePrintingConfig.cs deleted file mode 100644 index 7bf3d0e5a..000000000 --- a/ObjectPrinting/TypePrintingConfig.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; - -namespace ObjectPrinting; - -public class TypePrintingConfig -{ - private readonly PrintingConfig config; - - internal TypePrintingConfig(PrintingConfig config) - { - this.config = config; - } - - public PrintingConfig Using(Func serializer) - { - config.TypeSerializers[typeof(TProp)] = obj => serializer((TProp)obj); - return config; - } -} \ No newline at end of file From 6bd26bd1238b9e2b084b763e7bf7e22b5b70f4c4 Mon Sep 17 00:00:00 2001 From: Balashov2004 Date: Fri, 21 Nov 2025 17:29:17 +0500 Subject: [PATCH 05/13] dry fix 1 --- ObjectPrinting/EnumerablePrinter.cs | 55 +++++++++++ ObjectPrinting/MemberPrinter.cs | 48 ++++++++++ ObjectPrinting/ObjectSerializer.cs | 138 ++++------------------------ ObjectPrinting/TypeHelper.cs | 18 ++++ 4 files changed, 137 insertions(+), 122 deletions(-) create mode 100644 ObjectPrinting/EnumerablePrinter.cs create mode 100644 ObjectPrinting/MemberPrinter.cs create mode 100644 ObjectPrinting/TypeHelper.cs diff --git a/ObjectPrinting/EnumerablePrinter.cs b/ObjectPrinting/EnumerablePrinter.cs new file mode 100644 index 000000000..c716b4a65 --- /dev/null +++ b/ObjectPrinting/EnumerablePrinter.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using ObjectPrinting.Interface; + +namespace ObjectPrinting; + +internal class EnumerablePrinter +{ + public string PrintEnumerable( + IEnumerable enumerable, + Type type, + int level, + IPrintingConfigInternal config, + HashSet visited, + ObjectSerializer serializer) + { + var indent = new string('\t', level); + var sb = new StringBuilder(); + sb.AppendLine(type.Name); + + if (enumerable is IDictionary dict) + { + foreach (DictionaryEntry entry in dict) + { + sb.Append(indent + "\t"); + sb.Append($"[{entry.Key}] = {serializer.PrintInternal(entry.Value, level + 1, config, visited)}"); + } + } + else + { + int i = 0; + foreach (var item in enumerable) + { + sb.Append(indent + "\t"); + + var itemType = item.GetType(); + if (itemType.IsGenericType && itemType.GetGenericTypeDefinition() == typeof(KeyValuePair<,>)) + { + dynamic kv = item; + sb.Append($"[{kv.Key}] = {serializer.PrintInternal(kv.Value, level + 1, config, visited)}"); + } + else + { + sb.Append($"[{i}] = {serializer.PrintInternal(item, level + 1, config, visited)}"); + } + + i++; + } + } + + return sb.ToString(); + } +} diff --git a/ObjectPrinting/MemberPrinter.cs b/ObjectPrinting/MemberPrinter.cs new file mode 100644 index 000000000..b04e1e3b5 --- /dev/null +++ b/ObjectPrinting/MemberPrinter.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using NUnit.Framework.Internal; +using ObjectPrinting.Interface; + +namespace ObjectPrinting; + +internal class MemberPrinter +{ + public string PrintMember( + string memberName, + object value, + Type memberType, + IPrintingConfigInternal config, + int level, + HashSet visited, + ObjectSerializer serializer) + { + if (value == null) + return memberName + " = null" + Environment.NewLine; + + if (config.PropertySerializers.TryGetValue(memberName, out var serializerFunc)) + return memberName + " = " + serializerFunc(value) + Environment.NewLine; + + if (config.TypeSerializers.TryGetValue(memberType, out var typeSerializer)) + return memberName + " = " + typeSerializer(value) + Environment.NewLine; + + if (value is string s) + { + int? maxLen = null; + + if (config.TrimLengths.TryGetValue(memberName, out var propLen)) + maxLen = propLen; + else if (config.GlobalStringTrimLength.HasValue) + maxLen = config.GlobalStringTrimLength.Value; + + if (maxLen.HasValue && s.Length > maxLen.Value) + s = s.Substring(0, maxLen.Value); + + return memberName + " = " + s + Environment.NewLine; + } + + if (TypeHelper.IsSimpleType(memberType)) + return memberName + " = " + value + Environment.NewLine; + + return memberName + " = " + serializer.PrintInternal(value, level, config, visited); + } +} \ No newline at end of file diff --git a/ObjectPrinting/ObjectSerializer.cs b/ObjectPrinting/ObjectSerializer.cs index 0bdf0222c..9a2adaaed 100644 --- a/ObjectPrinting/ObjectSerializer.cs +++ b/ObjectPrinting/ObjectSerializer.cs @@ -10,13 +10,16 @@ namespace ObjectPrinting; public class ObjectSerializer { + private readonly MemberPrinter _memberPrinter = new MemberPrinter(); + private readonly EnumerablePrinter _enumerablePrinter = new EnumerablePrinter(); + public string Print(TOwner obj, PrintingConfig config) { var visited = new HashSet(new ReferenceEqualityComparer()); return PrintInternal(obj, 0, config, visited); } - private string PrintInternal( + internal string PrintInternal( object obj, int level, IPrintingConfigInternal config, @@ -27,39 +30,32 @@ private string PrintInternal( var type = obj.GetType(); - if (IsSimpleType(type)) + if (TypeHelper.IsSimpleType(type)) return obj + Environment.NewLine; - if (!type.IsValueType) - { - if (!visited.Add(obj)) - return $"" + Environment.NewLine; - } + if (!type.IsValueType && !visited.Add(obj)) + return $"" + Environment.NewLine; if (obj is IEnumerable numerable) - { - return PrintEnumerable(numerable, type, level, config, visited); - } + return _enumerablePrinter.PrintEnumerable(numerable, type, level, config, visited, this); var indentObj = new string('\t', level); var sbObj = new StringBuilder(); sbObj.AppendLine(type.Name); - + var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance) - .Where(p => p.GetIndexParameters().Length == 0); + .Where(p => p.GetIndexParameters().Length == 0); foreach (var property in properties) { if (config.ExcludedTypes.Contains(property.PropertyType)) continue; if (config.ExcludedProperties.Contains(property.Name)) continue; var value = property.GetValue(obj); - var printed = PrintMember(property.Name, value, property.PropertyType, - config, level + 1, visited); - - sbObj.Append(indentObj + "\t" + printed); + sbObj.Append(indentObj + "\t" + + _memberPrinter.PrintMember(property.Name, value, property.PropertyType, config, level + 1, visited, this)); } - + var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance); foreach (var field in fields) { @@ -67,112 +63,10 @@ private string PrintInternal( if (config.ExcludedProperties.Contains(field.Name)) continue; var value = field.GetValue(obj); - var printed = PrintMember(field.Name, value, field.FieldType, - config, level + 1, visited); - - sbObj.Append(indentObj + "\t" + printed); + sbObj.Append(indentObj + "\t" + + _memberPrinter.PrintMember(field.Name, value, field.FieldType, config, level + 1, visited, this)); } return sbObj.ToString(); } - - private string PrintMember( - string memberName, - object value, - Type memberType, - IPrintingConfigInternal config, - int level, - HashSet visited) - { - if (config.PropertySerializers.TryGetValue(memberName, out var serializer)) - { - var val = serializer(value); - return memberName + " = " + val + Environment.NewLine; - } - - if (config.TypeSerializers.TryGetValue(memberType, out var typeSerializer)) - { - var val = typeSerializer(value); - return memberName + " = " + val + Environment.NewLine; - } - - if (value is string s) - { - int? maxLen = null; - - if (config.TrimLengths.TryGetValue(memberName, out var propLen)) - maxLen = propLen; - - else if (config.GlobalStringTrimLength.HasValue) - maxLen = config.GlobalStringTrimLength.Value; - - if (maxLen.HasValue && s.Length > maxLen.Value) - s = s.Substring(0, maxLen.Value); - - return memberName + " = " + s + Environment.NewLine; - } - - if (IsSimpleType(memberType)) - return memberName + " = " + value + Environment.NewLine; - - return memberName + " = " + PrintInternal(value, level, config, visited); - } - - private string PrintEnumerable( - IEnumerable enumerable, - Type type, - int level, - IPrintingConfigInternal config, - HashSet visited) - { - var indent = new string('\t', level); - var sb = new StringBuilder(); - - sb.AppendLine(type.Name); - - if (enumerable is IDictionary nonGenericDict) - { - foreach (DictionaryEntry entry in nonGenericDict) - { - sb.Append(indent + "\t"); - sb.Append($"[{entry.Key}] = {PrintInternal(entry.Value, level + 1, config, visited)}"); - } - } - else - { - var i = 0; - foreach (var item in enumerable) - { - sb.Append(indent + "\t"); - - var itemType = item.GetType(); - if (itemType.IsGenericType && - itemType.GetGenericTypeDefinition() == typeof(KeyValuePair<,>)) - { - dynamic kv = item; - sb.Append($"[{kv.Key}] = {PrintInternal(kv.Value, level + 1, config, visited)}"); - } - else - { - sb.Append($"[{i}] = {PrintInternal(item, level + 1, config, visited)}"); - } - - i++; - } - } - - return sb.ToString(); - } - - private bool IsSimpleType(Type type) - { - if (type.IsPrimitive) return true; - if (type == typeof(string)) return true; - if (type == typeof(decimal)) return true; - if (type == typeof(DateTime)) return true; - if (type == typeof(TimeSpan)) return true; - if (type.IsEnum) return true; - - return false; - } -} \ No newline at end of file +} diff --git a/ObjectPrinting/TypeHelper.cs b/ObjectPrinting/TypeHelper.cs new file mode 100644 index 000000000..0b99e6618 --- /dev/null +++ b/ObjectPrinting/TypeHelper.cs @@ -0,0 +1,18 @@ +using System; + +namespace ObjectPrinting; + +public class TypeHelper +{ + public static bool IsSimpleType(Type type) + { + if (type.IsPrimitive) return true; + if (type == typeof(string)) return true; + if (type == typeof(decimal)) return true; + if (type == typeof(DateTime)) return true; + if (type == typeof(TimeSpan)) return true; + if (type.IsEnum) return true; + + return false; + } +} \ No newline at end of file From da663d6bfbc6e5bce882a36661e88e9a643261a0 Mon Sep 17 00:00:00 2001 From: Balashov2004 Date: Fri, 21 Nov 2025 17:53:53 +0500 Subject: [PATCH 06/13] 1 --- ObjectPrinting/Interface/IPrintingConfigInternal.cs | 2 +- ObjectPrinting/ObjectSerializer.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ObjectPrinting/Interface/IPrintingConfigInternal.cs b/ObjectPrinting/Interface/IPrintingConfigInternal.cs index 4e0a081d2..10e275e66 100644 --- a/ObjectPrinting/Interface/IPrintingConfigInternal.cs +++ b/ObjectPrinting/Interface/IPrintingConfigInternal.cs @@ -3,7 +3,7 @@ namespace ObjectPrinting.Interface; -public interface IPrintingConfigInternal +internal interface IPrintingConfigInternal { HashSet ExcludedTypes { get; } HashSet ExcludedProperties { get; } diff --git a/ObjectPrinting/ObjectSerializer.cs b/ObjectPrinting/ObjectSerializer.cs index 9a2adaaed..edea275da 100644 --- a/ObjectPrinting/ObjectSerializer.cs +++ b/ObjectPrinting/ObjectSerializer.cs @@ -38,6 +38,7 @@ internal string PrintInternal( if (obj is IEnumerable numerable) return _enumerablePrinter.PrintEnumerable(numerable, type, level, config, visited, this); + var indentObj = new string('\t', level); var sbObj = new StringBuilder(); From ca5e647d2f5927aa87dcebb2a023f544ccb492fc Mon Sep 17 00:00:00 2001 From: Balashov2004 Date: Fri, 21 Nov 2025 18:03:28 +0500 Subject: [PATCH 07/13] fix name --- ObjectPrinting/Tests/ObjectPrinterTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ObjectPrinting/Tests/ObjectPrinterTests.cs b/ObjectPrinting/Tests/ObjectPrinterTests.cs index 25e57822e..9c561c56f 100644 --- a/ObjectPrinting/Tests/ObjectPrinterTests.cs +++ b/ObjectPrinting/Tests/ObjectPrinterTests.cs @@ -78,7 +78,7 @@ public void ExtensionWithConfig() } [Test] - public void SerializeFieldsTest() + public void DefaultSerializationIncludesPropertiesAndFields() { var person = new Person { FirstName = "Ivanov", Name = "Alex", Age = 25 }; var printer = ObjectPrinter.For(); From d05a8343efaeeff77bad15a71a0b9288b318e8d8 Mon Sep 17 00:00:00 2001 From: Balashov2004 Date: Fri, 21 Nov 2025 18:38:13 +0500 Subject: [PATCH 08/13] 123 --- ObjectPrinting/EnumerablePrinter.cs | 2 +- .../Tests/ObjectPrinterAcceptanceTests.cs | 35 ------------------- ObjectPrinting/Tests/ObjectPrinterTests.cs | 15 +++++++- ObjectPrinting/Tests/Person.cs | 3 +- 4 files changed, 17 insertions(+), 38 deletions(-) delete mode 100644 ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs diff --git a/ObjectPrinting/EnumerablePrinter.cs b/ObjectPrinting/EnumerablePrinter.cs index c716b4a65..80a2f797d 100644 --- a/ObjectPrinting/EnumerablePrinter.cs +++ b/ObjectPrinting/EnumerablePrinter.cs @@ -52,4 +52,4 @@ public string PrintEnumerable( return sb.ToString(); } -} +} \ No newline at end of file diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs deleted file mode 100644 index 1c6626165..000000000 --- a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs +++ /dev/null @@ -1,35 +0,0 @@ -// using System; -// using NUnit.Framework; -// -// namespace ObjectPrinting.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 => $"Num is {i}") -// //3. Для числовых типов указать культуру -// .Printing(TypeNumber.WithDot) -// //4. Настроить сериализацию конкретного свойства -// .SelectMember(p => p.Name).Using(i => $"Age is {i}") -// //5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств) -// .SelectMember(p => p.Name).Trim(3) -// //6. Исключить из сериализации конкретного свойства -// .Excluding(info => info.Age); -// -// printer.PrintToString(person); -// //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию -// person.PrintToString(); -// //8. ...с конфигурированием -// person.PrintToString(info => info.Excluding(p => p.Age)); -// } -// } -// } \ No newline at end of file diff --git a/ObjectPrinting/Tests/ObjectPrinterTests.cs b/ObjectPrinting/Tests/ObjectPrinterTests.cs index 9c561c56f..5faf8d7ef 100644 --- a/ObjectPrinting/Tests/ObjectPrinterTests.cs +++ b/ObjectPrinting/Tests/ObjectPrinterTests.cs @@ -140,6 +140,19 @@ public void DictionarySerializationTest() Assert.That(result, Contains.Substring("[Alex] = 19")); Assert.That(result, Contains.Substring("[Ivan] = 21")); } - + [Test] + public void PrettySerializationTest() + { + var person = new Person { Name = "Alex", Age = 25, FirstName = "Ivanov" }; + var printer = ObjectPrinter.For(); + var printed = printer.PrintToString(person); + + var expected = "Person\n\tName = Alex\n\tAge = 25\n\tScore = 0\n\tId = Guid\n\tFirstName = Ivanov"; + + Assert.That( + printed.Replace("\r\n", "\n").Trim(), + Is.EqualTo(expected.Trim()) + ); + } } \ No newline at end of file diff --git a/ObjectPrinting/Tests/Person.cs b/ObjectPrinting/Tests/Person.cs index c7164809f..1b2342cbc 100644 --- a/ObjectPrinting/Tests/Person.cs +++ b/ObjectPrinting/Tests/Person.cs @@ -9,5 +9,6 @@ public class Person public int Age { get; set; } public double Score { get; set; } public Guid Id { get; set; } + } -} \ No newline at end of file +} From 56499f64d016011d6b8162c235d8eb84efcc31d7 Mon Sep 17 00:00:00 2001 From: Balashov2004 Date: Sat, 22 Nov 2025 10:26:46 +0500 Subject: [PATCH 09/13] Add GlobalTrim --- ObjectPrinting/PrintingConfig.cs | 2 +- ObjectPrinting/Solved/ObjectPrinter.cs | 0 ObjectPrinting/Solved/PrintingConfig.cs | 0 ObjectPrinting/Tests/ObjectPrinterTests.cs | 11 +++++++++++ 4 files changed, 12 insertions(+), 1 deletion(-) delete mode 100644 ObjectPrinting/Solved/ObjectPrinter.cs delete mode 100644 ObjectPrinting/Solved/PrintingConfig.cs diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index 6038043ea..df98036c4 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -18,7 +18,7 @@ public class PrintingConfig : IPrintingConfigInternal private ObjectSerializer objectSerializer = new ObjectSerializer(); - public PrintingConfig TrimStringsToLength(int maxLength) + public PrintingConfig TrimStringsGlobal(int maxLength) { GlobalStringTrimLength = maxLength; return this; diff --git a/ObjectPrinting/Solved/ObjectPrinter.cs b/ObjectPrinting/Solved/ObjectPrinter.cs deleted file mode 100644 index e69de29bb..000000000 diff --git a/ObjectPrinting/Solved/PrintingConfig.cs b/ObjectPrinting/Solved/PrintingConfig.cs deleted file mode 100644 index e69de29bb..000000000 diff --git a/ObjectPrinting/Tests/ObjectPrinterTests.cs b/ObjectPrinting/Tests/ObjectPrinterTests.cs index 5faf8d7ef..b1da29039 100644 --- a/ObjectPrinting/Tests/ObjectPrinterTests.cs +++ b/ObjectPrinting/Tests/ObjectPrinterTests.cs @@ -155,4 +155,15 @@ public void PrettySerializationTest() Is.EqualTo(expected.Trim()) ); } + + [Test] + public void TrimGlobalTest() + { + var person = new Person { Name = "Alexandr", Age = 25, FirstName = "Ivanov" }; + var printer = ObjectPrinter.For().TrimStringsGlobal(4); + var result = printer.PrintToString(person); + Assert.That(result, Contains.Substring("Alex")); + Assert.That(result, Contains.Substring("Ivan")); + + } } \ No newline at end of file From edaaf3efea0f7fca9abb7c0af49a55ca88eecf39 Mon Sep 17 00:00:00 2001 From: Balashov2004 Date: Sat, 22 Nov 2025 11:49:02 +0500 Subject: [PATCH 10/13] fix Culture --- ObjectPrinting/EnumerablePrinter.cs | 6 ++-- ObjectPrinting/MemberPrinter.cs | 5 ++- ObjectPrinting/MemberPrintingConfig.cs | 16 +++++++++ ObjectPrinting/ObjectPrinterExtensions.cs | 5 --- ObjectPrinting/ObjectSerializer.cs | 40 ++++++++++------------ ObjectPrinting/PrintingConfig.cs | 17 +++++---- ObjectPrinting/Tests/Format.cs | 9 +++++ ObjectPrinting/Tests/ObjectPrinterTests.cs | 27 +++++++++------ ObjectPrinting/TypePrintingConfig.cs | 31 +++++++++++++++++ 9 files changed, 107 insertions(+), 49 deletions(-) create mode 100644 ObjectPrinting/Tests/Format.cs create mode 100644 ObjectPrinting/TypePrintingConfig.cs diff --git a/ObjectPrinting/EnumerablePrinter.cs b/ObjectPrinting/EnumerablePrinter.cs index 80a2f797d..36a4c2ef3 100644 --- a/ObjectPrinting/EnumerablePrinter.cs +++ b/ObjectPrinting/EnumerablePrinter.cs @@ -18,6 +18,7 @@ public string PrintEnumerable( { var indent = new string('\t', level); var sb = new StringBuilder(); + sb.AppendLine(type.Name); if (enumerable is IDictionary dict) @@ -35,8 +36,9 @@ public string PrintEnumerable( { sb.Append(indent + "\t"); - var itemType = item.GetType(); - if (itemType.IsGenericType && itemType.GetGenericTypeDefinition() == typeof(KeyValuePair<,>)) + if (item != null && + item.GetType().IsGenericType && + item.GetType().GetGenericTypeDefinition() == typeof(KeyValuePair<,>)) { dynamic kv = item; sb.Append($"[{kv.Key}] = {serializer.PrintInternal(kv.Value, level + 1, config, visited)}"); diff --git a/ObjectPrinting/MemberPrinter.cs b/ObjectPrinting/MemberPrinter.cs index b04e1e3b5..93a5fdd9c 100644 --- a/ObjectPrinting/MemberPrinter.cs +++ b/ObjectPrinting/MemberPrinter.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using NUnit.Framework.Internal; using ObjectPrinting.Interface; namespace ObjectPrinting; @@ -29,8 +28,8 @@ public string PrintMember( { int? maxLen = null; - if (config.TrimLengths.TryGetValue(memberName, out var propLen)) - maxLen = propLen; + if (config.TrimLengths.TryGetValue(memberName, out var propTrim)) + maxLen = propTrim; else if (config.GlobalStringTrimLength.HasValue) maxLen = config.GlobalStringTrimLength.Value; diff --git a/ObjectPrinting/MemberPrintingConfig.cs b/ObjectPrinting/MemberPrintingConfig.cs index 411cd25d7..642636386 100644 --- a/ObjectPrinting/MemberPrintingConfig.cs +++ b/ObjectPrinting/MemberPrintingConfig.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; namespace ObjectPrinting; @@ -21,7 +22,22 @@ public PrintingConfig Using(Func serializer) public PrintingConfig Trim(int maxLength) { + if (typeof(TProp) != typeof(string)) + throw new InvalidOperationException("Trim can be used only on string properties"); + config.TrimLengths[memberName] = maxLength; return config; } + + public PrintingConfig Using(CultureInfo culture) + { + if (!typeof(IFormattable).IsAssignableFrom(typeof(TProp))) + throw new InvalidOperationException( + "Culture can be applied only to IFormattable properties"); + + config.PropertySerializers[memberName] = obj => + ((IFormattable)obj).ToString(null, culture); + + return config; + } } \ No newline at end of file diff --git a/ObjectPrinting/ObjectPrinterExtensions.cs b/ObjectPrinting/ObjectPrinterExtensions.cs index 56027a5e0..0b5342eb5 100644 --- a/ObjectPrinting/ObjectPrinterExtensions.cs +++ b/ObjectPrinting/ObjectPrinterExtensions.cs @@ -4,11 +4,6 @@ namespace ObjectPrinting; public static class ObjectPrinterExtensions { - public static string PrintToString(this T obj) - { - return new PrintingConfig().PrintToString(obj); - } - public static string PrintToString( this T obj, Func, PrintingConfig> config) { diff --git a/ObjectPrinting/ObjectSerializer.cs b/ObjectPrinting/ObjectSerializer.cs index edea275da..fca6dcd35 100644 --- a/ObjectPrinting/ObjectSerializer.cs +++ b/ObjectPrinting/ObjectSerializer.cs @@ -1,7 +1,6 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Linq; using System.Reflection; using System.Text; using ObjectPrinting.Interface; @@ -10,9 +9,9 @@ namespace ObjectPrinting; public class ObjectSerializer { - private readonly MemberPrinter _memberPrinter = new MemberPrinter(); - private readonly EnumerablePrinter _enumerablePrinter = new EnumerablePrinter(); - + private readonly MemberPrinter memberPrinter = new MemberPrinter(); + private readonly EnumerablePrinter enumerablePrinter = new EnumerablePrinter(); + public string Print(TOwner obj, PrintingConfig config) { var visited = new HashSet(new ReferenceEqualityComparer()); @@ -36,38 +35,35 @@ internal string PrintInternal( if (!type.IsValueType && !visited.Add(obj)) return $"" + Environment.NewLine; - if (obj is IEnumerable numerable) - return _enumerablePrinter.PrintEnumerable(numerable, type, level, config, visited, this); - + if (obj is IEnumerable enumerable) + return enumerablePrinter.PrintEnumerable(enumerable, type, level, config, visited, this); + + var indent = new string('\t', level); + var sb = new StringBuilder(); - var indentObj = new string('\t', level); - var sbObj = new StringBuilder(); + sb.AppendLine(type.Name); - sbObj.AppendLine(type.Name); - - var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance) - .Where(p => p.GetIndexParameters().Length == 0); - foreach (var property in properties) + foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) { + if (property.GetIndexParameters().Length > 0) continue; if (config.ExcludedTypes.Contains(property.PropertyType)) continue; if (config.ExcludedProperties.Contains(property.Name)) continue; var value = property.GetValue(obj); - sbObj.Append(indentObj + "\t" + - _memberPrinter.PrintMember(property.Name, value, property.PropertyType, config, level + 1, visited, this)); + sb.Append(indent + "\t" + + memberPrinter.PrintMember(property.Name, value, property.PropertyType, config, level + 1, visited, this)); } - - var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance); - foreach (var field in fields) + + foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Instance)) { if (config.ExcludedTypes.Contains(field.FieldType)) continue; if (config.ExcludedProperties.Contains(field.Name)) continue; var value = field.GetValue(obj); - sbObj.Append(indentObj + "\t" + - _memberPrinter.PrintMember(field.Name, value, field.FieldType, config, level + 1, visited, this)); + sb.Append(indent + "\t" + + memberPrinter.PrintMember(field.Name, value, field.FieldType, config, level + 1, visited, this)); } - return sbObj.ToString(); + return sb.ToString(); } } diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index df98036c4..e897d8f67 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq.Expressions; -using System.Reflection; using ObjectPrinting.Interface; namespace ObjectPrinting; @@ -15,16 +15,15 @@ public class PrintingConfig : IPrintingConfigInternal public Dictionary> PropertySerializers { get; } = new(); public Dictionary TrimLengths { get; } = new(); public int? GlobalStringTrimLength { get; private set; } - - private ObjectSerializer objectSerializer = new ObjectSerializer(); - + + private readonly ObjectSerializer objectSerializer = new ObjectSerializer(); + public PrintingConfig TrimStringsGlobal(int maxLength) { GlobalStringTrimLength = maxLength; return this; } - public PrintingConfig Excluding() { ExcludedTypes.Add(typeof(TProp)); @@ -43,6 +42,11 @@ public PrintingConfig Printing(Func selector) TypeSerializers[typeof(TProp)] = o => selector((TProp)o); return this; } + + public TypePrintingConfig Printing() + { + return new TypePrintingConfig(this); + } public MemberPrintingConfig SelectMember( Expression> selector) @@ -55,4 +59,5 @@ public string PrintToString(TOwner obj) { return objectSerializer.Print(obj, this); } -} \ No newline at end of file + +} diff --git a/ObjectPrinting/Tests/Format.cs b/ObjectPrinting/Tests/Format.cs new file mode 100644 index 000000000..8ea05e5d5 --- /dev/null +++ b/ObjectPrinting/Tests/Format.cs @@ -0,0 +1,9 @@ +using System; + +namespace ObjectPrinting.Tests; + +public class Format +{ + public double Value { get; set; } + public DateTime Date { get; set; } +} \ No newline at end of file diff --git a/ObjectPrinting/Tests/ObjectPrinterTests.cs b/ObjectPrinting/Tests/ObjectPrinterTests.cs index b1da29039..152337ea4 100644 --- a/ObjectPrinting/Tests/ObjectPrinterTests.cs +++ b/ObjectPrinting/Tests/ObjectPrinterTests.cs @@ -1,6 +1,7 @@  using System; using System.Collections.Generic; +using System.Globalization; using NUnit.Framework; namespace ObjectPrinting.Tests; @@ -52,18 +53,23 @@ public void TrimTest() } [Test] - public void CustomDoubleTest() + public void Printing_WithCulture_ForIFormattableTypes() { - var person = new Person { Name = "Alex", Score = 12.5 }; - var printer1 = ObjectPrinter.For() - .Printing(TypeNumber.WithDot); - string printed1 = printer1.PrintToString(person);; - Assert.That(printed1, Contains.Substring("12.5")); + var obj = new Format() + { + Value = 1234.56, + Date = new DateTime(2024, 5, 25, 13, 45, 0) + }; + + var printer = ObjectPrinter.For() + .Printing().Using(CultureInfo.InvariantCulture) + .Printing().Using(new CultureInfo("ru-RU")); + + string result = printer.PrintToString(obj); - var printer2 = ObjectPrinter.For() - .Printing(TypeNumber.WithComma); - var printed2 = printer2.PrintToString(person); - Assert.That(printed2, Contains.Substring("12,5")); + Assert.That(result, Does.Contain("1234.56")); + Assert.That(result, Does.Contain("25.05.2024")); + Assert.That(result, Does.Not.Contain("5/25/2024")); } [Test] @@ -164,6 +170,5 @@ public void TrimGlobalTest() var result = printer.PrintToString(person); Assert.That(result, Contains.Substring("Alex")); Assert.That(result, Contains.Substring("Ivan")); - } } \ No newline at end of file diff --git a/ObjectPrinting/TypePrintingConfig.cs b/ObjectPrinting/TypePrintingConfig.cs new file mode 100644 index 000000000..2d22ab501 --- /dev/null +++ b/ObjectPrinting/TypePrintingConfig.cs @@ -0,0 +1,31 @@ +using System; +using System.Globalization; + +namespace ObjectPrinting; + +public class TypePrintingConfig +{ + private readonly PrintingConfig config; + + internal TypePrintingConfig(PrintingConfig config) + { + this.config = config; + } + + public PrintingConfig Using(Func serializer) + { + config.TypeSerializers[typeof(TProp)] = o => serializer((TProp)o); + return config; + } + + public PrintingConfig Using(CultureInfo culture) + { + if (!typeof(IFormattable).IsAssignableFrom(typeof(TProp))) + throw new InvalidOperationException("Culture can be applied only to IFormattable"); + + config.TypeSerializers[typeof(TProp)] = + o => ((IFormattable)o).ToString(null, culture); + + return config; + } +} \ No newline at end of file From 5280988aa394d37c3319fe616df355a2f0a016fc Mon Sep 17 00:00:00 2001 From: Balashov2004 Date: Sat, 22 Nov 2025 12:54:46 +0500 Subject: [PATCH 11/13] delete throw --- ObjectPrinting/FormattableMemberConfig.cs | 26 +++++++++++++++++ .../Interface/IFormattableMemberConfig.cs | 10 +++++++ ObjectPrinting/Interface/IMemberConfig.cs | 8 +++++ .../Interface/IStringMemberConfig.cs | 6 ++++ ObjectPrinting/MemberPrintingConfig.cs | 29 +++++-------------- ObjectPrinting/StringMemberConfig.cs | 21 ++++++++++++++ ObjectPrinting/Tests/ObjectPrinterTests.cs | 2 +- 7 files changed, 80 insertions(+), 22 deletions(-) create mode 100644 ObjectPrinting/FormattableMemberConfig.cs create mode 100644 ObjectPrinting/Interface/IFormattableMemberConfig.cs create mode 100644 ObjectPrinting/Interface/IMemberConfig.cs create mode 100644 ObjectPrinting/Interface/IStringMemberConfig.cs create mode 100644 ObjectPrinting/StringMemberConfig.cs diff --git a/ObjectPrinting/FormattableMemberConfig.cs b/ObjectPrinting/FormattableMemberConfig.cs new file mode 100644 index 000000000..0935f12a4 --- /dev/null +++ b/ObjectPrinting/FormattableMemberConfig.cs @@ -0,0 +1,26 @@ +using System; +using System.Globalization; +using ObjectPrinting.Interface; + +namespace ObjectPrinting; + +public class FormattableMemberConfig : + IFormattableMemberConfig + where TProp : IFormattable +{ + private readonly PrintingConfig config; + private readonly string memberName; + + public FormattableMemberConfig(PrintingConfig config, string memberName) + { + this.config = config; + this.memberName = memberName; + } + + public PrintingConfig Using(CultureInfo culture) + { + config.PropertySerializers[memberName] = obj => + ((IFormattable)obj).ToString(null, culture); + return config; + } +} diff --git a/ObjectPrinting/Interface/IFormattableMemberConfig.cs b/ObjectPrinting/Interface/IFormattableMemberConfig.cs new file mode 100644 index 000000000..1e10041dd --- /dev/null +++ b/ObjectPrinting/Interface/IFormattableMemberConfig.cs @@ -0,0 +1,10 @@ +using System; +using System.Globalization; + +namespace ObjectPrinting.Interface; + +public interface IFormattableMemberConfig + where TProp : IFormattable +{ + PrintingConfig Using(CultureInfo culture); +} \ No newline at end of file diff --git a/ObjectPrinting/Interface/IMemberConfig.cs b/ObjectPrinting/Interface/IMemberConfig.cs new file mode 100644 index 000000000..1f00e1a0b --- /dev/null +++ b/ObjectPrinting/Interface/IMemberConfig.cs @@ -0,0 +1,8 @@ +using System; + +namespace ObjectPrinting.Interface; + +public interface IMemberConfig +{ + PrintingConfig Using(Func serializer); +} \ No newline at end of file diff --git a/ObjectPrinting/Interface/IStringMemberConfig.cs b/ObjectPrinting/Interface/IStringMemberConfig.cs new file mode 100644 index 000000000..f43a5b4b6 --- /dev/null +++ b/ObjectPrinting/Interface/IStringMemberConfig.cs @@ -0,0 +1,6 @@ +namespace ObjectPrinting.Interface; + +public interface IStringMemberConfig +{ + PrintingConfig Trim(int maxLength); +} \ No newline at end of file diff --git a/ObjectPrinting/MemberPrintingConfig.cs b/ObjectPrinting/MemberPrintingConfig.cs index 642636386..02a103fc6 100644 --- a/ObjectPrinting/MemberPrintingConfig.cs +++ b/ObjectPrinting/MemberPrintingConfig.cs @@ -1,5 +1,6 @@ using System; using System.Globalization; +using ObjectPrinting.Interface; namespace ObjectPrinting; @@ -13,31 +14,17 @@ internal MemberPrintingConfig(PrintingConfig config, string memberName) this.config = config; this.memberName = memberName; } - + public PrintingConfig Using(Func serializer) { config.PropertySerializers[memberName] = obj => serializer((TProp)obj); return config; } - - public PrintingConfig Trim(int maxLength) - { - if (typeof(TProp) != typeof(string)) - throw new InvalidOperationException("Trim can be used only on string properties"); - - config.TrimLengths[memberName] = maxLength; - return config; - } - - public PrintingConfig Using(CultureInfo culture) + + public IStringMemberConfig AsString() { - if (!typeof(IFormattable).IsAssignableFrom(typeof(TProp))) - throw new InvalidOperationException( - "Culture can be applied only to IFormattable properties"); - - config.PropertySerializers[memberName] = obj => - ((IFormattable)obj).ToString(null, culture); - - return config; + return typeof(TProp) == typeof(string) + ? new StringMemberConfig(config, memberName) + : throw new InvalidOperationException("Property is not string"); } -} \ No newline at end of file +} diff --git a/ObjectPrinting/StringMemberConfig.cs b/ObjectPrinting/StringMemberConfig.cs new file mode 100644 index 000000000..7d0b37cd9 --- /dev/null +++ b/ObjectPrinting/StringMemberConfig.cs @@ -0,0 +1,21 @@ +using ObjectPrinting.Interface; + +namespace ObjectPrinting; + +public class StringMemberConfig : IStringMemberConfig +{ + private readonly PrintingConfig config; + private readonly string memberName; + + public StringMemberConfig(PrintingConfig config, string memberName) + { + this.config = config; + this.memberName = memberName; + } + + public PrintingConfig Trim(int maxLength) + { + config.TrimLengths[memberName] = maxLength; + return config; + } +} diff --git a/ObjectPrinting/Tests/ObjectPrinterTests.cs b/ObjectPrinting/Tests/ObjectPrinterTests.cs index 152337ea4..140a9f51e 100644 --- a/ObjectPrinting/Tests/ObjectPrinterTests.cs +++ b/ObjectPrinting/Tests/ObjectPrinterTests.cs @@ -47,7 +47,7 @@ public void TrimTest() { var person = new Person { Name = "Alex", Age = 25, Id = Guid.NewGuid() }; var printer = ObjectPrinter.For() - .SelectMember(p => p.Name).Trim(3); + .SelectMember(p => p.Name).AsString().Trim(3); var printed = printer.PrintToString(person); Assert.That(printed, Contains.Substring("Name = Ale")); } From f638fe6a3b1906e1c850b6a108b5bbc6aa460b5c Mon Sep 17 00:00:00 2001 From: Balashov2004 Date: Sat, 22 Nov 2025 13:07:31 +0500 Subject: [PATCH 12/13] add test --- .../Tests/ObjectPrinterNestedTests.cs | 62 +++++++++++++++++++ ObjectPrinting/Tests/Person.cs | 38 +++++++++--- 2 files changed, 92 insertions(+), 8 deletions(-) create mode 100644 ObjectPrinting/Tests/ObjectPrinterNestedTests.cs diff --git a/ObjectPrinting/Tests/ObjectPrinterNestedTests.cs b/ObjectPrinting/Tests/ObjectPrinterNestedTests.cs new file mode 100644 index 000000000..b0c8351a9 --- /dev/null +++ b/ObjectPrinting/Tests/ObjectPrinterNestedTests.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using NUnit.Framework; + +namespace ObjectPrinting.Tests; + +[TestFixture] +public class ObjectPrinterNestedTests +{ + [Test] + public void PersonWithNestedCompanyAndAddressTest() + { + var person = new Person + { + FirstName = "Ivanov", + Name = "Alex", + Age = 21, + Score = 99.9, + Id = Guid.NewGuid(), + Company = new Company + { + CompanyName = "Kontur", + Industry = "IT", + Address = new Address + { + Street = "Maloprudnia 12", + City = "EKB", + Country = "Russia" + } + }, + Address = new Address + { + Street = "88", + City = "Moscow", + Country = "Russia" + } + }; + + var printed = ObjectPrinter.For() + .Printing(TypeNumber.WithDot) + .PrintToString(person); + + Assert.That(printed, Contains.Substring("FirstName = Ivanov")); + Assert.That(printed, Contains.Substring("Name = Alex")); + Assert.That(printed, Contains.Substring("Age = 21")); + Assert.That(printed, Contains.Substring("Score = 99.9")); + Assert.That(printed, Contains.Substring("Id")); + + Assert.That(printed, Contains.Substring("Company")); + Assert.That(printed, Contains.Substring("CompanyName = Kontur")); + Assert.That(printed, Contains.Substring("Industry = IT")); + + Assert.That(printed, Contains.Substring("Street = Maloprudnia 12")); + Assert.That(printed, Contains.Substring("City = EKB")); + Assert.That(printed, Contains.Substring("Country = Russia")); + + Assert.That(printed, Contains.Substring("Address")); + Assert.That(printed, Contains.Substring("Street = 88")); + Assert.That(printed, Contains.Substring("City = Moscow")); + Assert.That(printed, Contains.Substring("Country = Russia")); + } +} \ No newline at end of file diff --git a/ObjectPrinting/Tests/Person.cs b/ObjectPrinting/Tests/Person.cs index 1b2342cbc..6c30924d8 100644 --- a/ObjectPrinting/Tests/Person.cs +++ b/ObjectPrinting/Tests/Person.cs @@ -1,14 +1,36 @@ using System; +using System.Collections.Generic; -namespace ObjectPrinting.Tests +namespace ObjectPrinting.Tests; + +public class Person { - public class Person + public string FirstName; + public Company Company; + public Address Address; + public List Tags; + + public string Name { get; set; } + public int Age { get; set; } + public double Score { get; set; } + public Guid Id { get; set; } + + public Person() { - public string FirstName; - public string Name { get; set; } - public int Age { get; set; } - public double Score { get; set; } - public Guid Id { get; set; } - + Tags = new List(); } } + +public class Company +{ + public string CompanyName { get; set; } + public string Industry { get; set; } + public Address Address { get; set; } +} + +public class Address +{ + public string Street { get; set; } + public string City { get; set; } + public string Country { get; set; } +} From 437537cca45cb3ad0a020950d7b7438945d01a2b Mon Sep 17 00:00:00 2001 From: Balashov2004 Date: Sat, 22 Nov 2025 17:02:41 +0500 Subject: [PATCH 13/13] int -> var and other small fix --- ObjectPrinting/EnumerablePrinter.cs | 2 +- ObjectPrinting/Tests/ObjectPrinterNestedTests.cs | 3 ++- ObjectPrinting/TypeNumber.cs | 12 ------------ 3 files changed, 3 insertions(+), 14 deletions(-) delete mode 100644 ObjectPrinting/TypeNumber.cs diff --git a/ObjectPrinting/EnumerablePrinter.cs b/ObjectPrinting/EnumerablePrinter.cs index 36a4c2ef3..dc9aff465 100644 --- a/ObjectPrinting/EnumerablePrinter.cs +++ b/ObjectPrinting/EnumerablePrinter.cs @@ -31,7 +31,7 @@ public string PrintEnumerable( } else { - int i = 0; + var i = 0; foreach (var item in enumerable) { sb.Append(indent + "\t"); diff --git a/ObjectPrinting/Tests/ObjectPrinterNestedTests.cs b/ObjectPrinting/Tests/ObjectPrinterNestedTests.cs index b0c8351a9..0072bcd8f 100644 --- a/ObjectPrinting/Tests/ObjectPrinterNestedTests.cs +++ b/ObjectPrinting/Tests/ObjectPrinterNestedTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using NUnit.Framework; namespace ObjectPrinting.Tests; @@ -37,7 +38,7 @@ public void PersonWithNestedCompanyAndAddressTest() }; var printed = ObjectPrinter.For() - .Printing(TypeNumber.WithDot) + .Printing().Using(CultureInfo.InvariantCulture) .PrintToString(person); Assert.That(printed, Contains.Substring("FirstName = Ivanov")); diff --git a/ObjectPrinting/TypeNumber.cs b/ObjectPrinting/TypeNumber.cs deleted file mode 100644 index 886c8e9af..000000000 --- a/ObjectPrinting/TypeNumber.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Globalization; - -namespace ObjectPrinting; - -public class TypeNumber -{ - public static Func WithDot => - d => d.ToString(CultureInfo.InvariantCulture); - public static Func WithComma => - d => d.ToString(new CultureInfo("ru-RU")); -} \ No newline at end of file