diff --git a/ObjectPrinting/Context/PropertyContext/IPropertyPrinterConfigContext.cs b/ObjectPrinting/Context/PropertyContext/IPropertyPrinterConfigContext.cs new file mode 100644 index 000000000..ea0c1c31a --- /dev/null +++ b/ObjectPrinting/Context/PropertyContext/IPropertyPrinterConfigContext.cs @@ -0,0 +1,10 @@ +using System; + +namespace ObjectPrinting.Context; + +public interface IPropertyPrinterConfigContext +{ + IPropertyPrinterConfigContext SetSerializer(Func serializer); + IPropertyPrinterConfigContext ExcludeProperty(); + PrintingConfig End(); +} \ No newline at end of file diff --git a/ObjectPrinting/Context/PropertyContext/ISrtingPropertyContext.cs b/ObjectPrinting/Context/PropertyContext/ISrtingPropertyContext.cs new file mode 100644 index 000000000..85ff2a4a9 --- /dev/null +++ b/ObjectPrinting/Context/PropertyContext/ISrtingPropertyContext.cs @@ -0,0 +1,6 @@ +namespace ObjectPrinting.Context; + +public interface IStringPropertyPrinterConfigContext : IPropertyPrinterConfigContext +{ + IStringPropertyPrinterConfigContext TrimToLength(int maxLength); +} \ No newline at end of file diff --git a/ObjectPrinting/Context/PropertyContext/PropertyContext.cs b/ObjectPrinting/Context/PropertyContext/PropertyContext.cs new file mode 100644 index 000000000..65a869408 --- /dev/null +++ b/ObjectPrinting/Context/PropertyContext/PropertyContext.cs @@ -0,0 +1,33 @@ +using System; +using ObjectPrinting.Context; + +namespace ObjectPrinting; + +public class PropertyContext : IPropertyPrinterConfigContext +{ + private readonly PrintingConfig parent; + private readonly string propertyName; + + internal PropertyContext(PrintingConfig parent, string propertyName) + { + this.parent = parent; + this.propertyName = propertyName; + } + + public IPropertyPrinterConfigContext SetSerializer(Func serializer) + { + parent.SetPropertySerializer(propertyName, serializer); + return this; + } + + public IPropertyPrinterConfigContext ExcludeProperty() + { + parent.ExcludeProperty(propertyName); + return this; + } + + public PrintingConfig End() + { + return parent; + } +} \ No newline at end of file diff --git a/ObjectPrinting/Context/PropertyContext/StringPropertyContext.cs b/ObjectPrinting/Context/PropertyContext/StringPropertyContext.cs new file mode 100644 index 000000000..63c7c23b8 --- /dev/null +++ b/ObjectPrinting/Context/PropertyContext/StringPropertyContext.cs @@ -0,0 +1,49 @@ +using System; +using ObjectPrinting.Context; + +namespace ObjectPrinting; + +public class StringPropertyContext : IStringPropertyPrinterConfigContext +{ + private readonly PrintingConfig parent; + private readonly string propertyName; + + internal StringPropertyContext(PrintingConfig parent, string propertyName) + { + this.parent = parent; + this.propertyName = propertyName; + } + + public IStringPropertyPrinterConfigContext Set(Func serializer) + { + parent.SetPropertySerializer(propertyName, serializer); + return this; + } + + public IStringPropertyPrinterConfigContext Exclude() + { + parent.ExcludeProperty(propertyName); + return this; + } + + public IStringPropertyPrinterConfigContext TrimToLength(int maxLength) + { + parent.TrimStringProperty(propertyName, maxLength); + return this; + } + + public PrintingConfig End() + { + return parent; + } + + IPropertyPrinterConfigContext IPropertyPrinterConfigContext.SetSerializer(Func serializer) + { + return Set(serializer); + } + + IPropertyPrinterConfigContext IPropertyPrinterConfigContext.ExcludeProperty() + { + return Exclude(); + } +} \ No newline at end of file diff --git a/ObjectPrinting/Context/TypeContext/BaseContext.cs b/ObjectPrinting/Context/TypeContext/BaseContext.cs new file mode 100644 index 000000000..67d19caf6 --- /dev/null +++ b/ObjectPrinting/Context/TypeContext/BaseContext.cs @@ -0,0 +1,37 @@ +using System; +using ObjectPrinting.Context; + +namespace ObjectPrinting; + +public class BaseContext : IPrintingConfigContext +{ + private readonly PrintingConfig parent; + + internal BaseContext(PrintingConfig parent) + { + this.parent = parent; + } + + public IPrintingConfigContext SetSerializer(Func serializer) + { + parent.SetTypeSerializer(serializer); + return this; + } + + public IPrintingConfigContext UsingCulture(IFormatProvider culture) + { + parent.SetNumericCulture(culture); + return this; + } + + public IPrintingConfigContext ExcludeType() + { + parent.ExcludeType(); + return this; + } + + public PrintingConfig End() + { + return parent; + } +} \ No newline at end of file diff --git a/ObjectPrinting/Context/TypeContext/IPrintingConfigContext.cs b/ObjectPrinting/Context/TypeContext/IPrintingConfigContext.cs new file mode 100644 index 000000000..ffc2e011d --- /dev/null +++ b/ObjectPrinting/Context/TypeContext/IPrintingConfigContext.cs @@ -0,0 +1,11 @@ +using System; + +namespace ObjectPrinting.Context; + +public interface IPrintingConfigContext +{ + IPrintingConfigContext SetSerializer(Func serializer); + IPrintingConfigContext UsingCulture(IFormatProvider culture); + IPrintingConfigContext ExcludeType(); + PrintingConfig End(); +} \ No newline at end of file diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index a9e082117..885e0a8ff 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -1,41 +1,265 @@ using System; +using System.Collections; +using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; +using System.Reflection; using System.Text; +using ObjectPrinting.Context; -namespace ObjectPrinting +namespace ObjectPrinting; + +public class PrintingConfig { - public class PrintingConfig + private static readonly Type[] FinalTypes = + [ + typeof(int), typeof(double), typeof(float), typeof(string), + typeof(DateTime), typeof(TimeSpan) + ]; + + private readonly Dictionary> typeSerializers = new(); + private readonly Dictionary numericCultures = new(); + private readonly HashSet excludedTypes = []; + + private readonly Dictionary> propertySerializers = new(); + private readonly Dictionary stringMaxLengths = new(); + private readonly HashSet excludedProperties = []; + + internal void SetTypeSerializer(Func serializer) + { + typeSerializers[typeof(T)] = o => serializer((T)o); + } + + internal void SetNumericCulture(IFormatProvider culture) + { + numericCultures[typeof(T)] = culture; + } + + internal void ExcludeType() + { + excludedTypes.Add(typeof(T)); + } + + internal void SetPropertySerializer(string propertyName, Func serializer) { - public string PrintToString(TOwner obj) + propertySerializers[propertyName] = o => serializer((T)o); + } + + internal void TrimStringProperty(string propertyName, int maxLength) + { + stringMaxLengths[propertyName] = maxLength; + } + + internal void ExcludeProperty(string propertyName) + { + excludedProperties.Add(propertyName); + } + + public string PrintToString(TOwner obj) + { + var visited = new HashSet(ReferenceEqualityComparer.Instance); + return PrintToString(obj, 0, visited, ""); + } + + private string PrintToString(object obj, int nestingLevel, HashSet visited, string currentPath) + { + if (obj == null) + return "null" + Environment.NewLine; + + var type = obj.GetType(); + + if (excludedTypes.Contains(type)) + return string.Empty; + + if (TryPrintSimple(type, obj, out var simple)) + return simple; + + if (!visited.Add(obj)) + return "circular dependency" + Environment.NewLine; + + if (obj is IDictionary dict) + return PrintDictionary(dict, type, nestingLevel, visited, currentPath); + + if (typeof(IEnumerable).IsAssignableFrom(type)) + return PrintEnumerable((IEnumerable)obj, type, nestingLevel, visited, currentPath); + + return PrintComplexObject(obj, type, nestingLevel, visited, currentPath); + } + + private string PrintDictionary(IDictionary dict, Type type, int nestingLevel, HashSet visited, string currentPath) + { + var sb = new StringBuilder(); + var ident = new string('\t', nestingLevel + 1); + sb.AppendLine(type.Name); + + foreach (DictionaryEntry entry in dict) + { + var keyPath = currentPath + "[key]"; + var valuePath = currentPath + "[value]"; + + var keyPrinted = PrintToString(entry.Key, nestingLevel + 1, visited, keyPath).TrimEnd('\r', '\n'); + var valuePrinted = PrintToString(entry.Value, nestingLevel + 1, visited, valuePath).TrimEnd('\r', '\n'); + + sb.AppendLine($"{ident}Key = {keyPrinted}, Value = {valuePrinted}"); + } + + return sb.ToString(); + } + + + private bool TryPrintSimple(Type type, object obj, out string result) + { + if (typeSerializers.TryGetValue(type, out var typeSerializer)) { - return PrintToString(obj, 0); + result = typeSerializer(obj) + Environment.NewLine; + return true; } - private string PrintToString(object obj, int nestingLevel) + if (FinalTypes.Contains(type)) { - //TODO apply configurations - if (obj == null) - return "null" + Environment.NewLine; + if (obj is IFormattable f && numericCultures.TryGetValue(type, out var culture)) + result = f.ToString(null, culture) + Environment.NewLine; + else + result = obj + 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()) + return true; + } + + result = null; + return false; + } + + private string PrintComplexObject(object obj, Type type, int nestingLevel, HashSet visited, string currentPath) + { + var ident = new string('\t', nestingLevel + 1); + var sb = new StringBuilder(); + sb.AppendLine(type.Name); + + var flags = BindingFlags.Public | BindingFlags.Instance; + + foreach (var propertyInfo in type.GetProperties(flags)) + { + var propName = propertyInfo.Name; + var propType = propertyInfo.PropertyType; + + var fullPropPath = string.IsNullOrEmpty(currentPath) + ? propName + : currentPath + "." + propName; + + if (excludedProperties.Contains(fullPropPath) || excludedTypes.Contains(propType)) + continue; + + var value = propertyInfo.GetValue(obj); + + if (TryPrintPropertyWithCustomOrTrim(sb, ident, fullPropPath, propType, value)) + continue; + + sb.Append(ident + propName + " = " + + PrintToString(value, nestingLevel + 1, visited, fullPropPath)); + } + + foreach (var fieldInfo in type.GetFields(flags)) + { + var fieldName = fieldInfo.Name; + var fieldType = fieldInfo.FieldType; + + var fullFieldPath = string.IsNullOrEmpty(currentPath) + ? fieldName + : currentPath + "." + fieldName; + + if (excludedProperties.Contains(fullFieldPath) || excludedTypes.Contains(fieldType)) + continue; + + var value = fieldInfo.GetValue(obj); + + if (TryPrintPropertyWithCustomOrTrim(sb, ident, fullFieldPath, fieldType, value)) + continue; + + sb.Append(ident + fieldName + " = " + + PrintToString(value, nestingLevel + 1, visited, fullFieldPath)); + } + + return sb.ToString(); + } + + private bool TryPrintPropertyWithCustomOrTrim(StringBuilder sb, string ident, string fullPropPath, Type propType, object value) + { + if (propertySerializers.TryGetValue(fullPropPath, out var propSerializer)) + { + sb.Append(ident + fullPropPath + " = " + + propSerializer(value) + Environment.NewLine); + return true; + } + + if (propType != typeof(string) || !stringMaxLengths.TryGetValue(fullPropPath, out var maxLength)) + return false; + + var s = (string)value; + if (s != null && s.Length > maxLength) + s = s[..maxLength]; + + sb.Append(ident + fullPropPath + " = " + s + Environment.NewLine); + return true; + } + + private string PrintEnumerable(IEnumerable enumerable, Type type, int nestingLevel, HashSet visited, string currentPath) + { + var sb = new StringBuilder(); + var ident = new string('\t', nestingLevel + 1); + sb.AppendLine(type.Name); + var index = 0; + + foreach (var item in enumerable) + { + var itemPath = currentPath + "[" + index + "]"; + index++; + + if (item == null) { - sb.Append(identation + propertyInfo.Name + " = " + - PrintToString(propertyInfo.GetValue(obj), - nestingLevel + 1)); + sb.Append(ident + "null" + Environment.NewLine); + continue; } - return sb.ToString(); + var itemType = item.GetType(); + + if (excludedTypes.Contains(itemType)) + continue; + + var printed = PrintToString(item, nestingLevel + 1, visited, itemPath); + sb.Append(ident + printed); + } + + return sb.ToString(); + } + + public IPrintingConfigContext For() + { + return new BaseContext(this); + } + + public IPropertyPrinterConfigContext For(Expression> propertySelector) + { + var propPath = GetPropertyPath(propertySelector); + return new PropertyContext(this, propPath); + } + + public IStringPropertyPrinterConfigContext For(Expression> propertySelector) + { + var propPath = GetPropertyPath(propertySelector); + return new StringPropertyContext(this, propPath); + } + + private static string GetPropertyPath(LambdaExpression expression) + { + var parts = new List(); + var current = expression.Body; + + while (current is MemberExpression m) + { + parts.Add(m.Member.Name); + current = m.Expression; } + + parts.Reverse(); + return string.Join(".", parts); } -} \ No newline at end of file +} diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs index 4c8b2445c..dbe8e6049 100644 --- a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs +++ b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs @@ -1,27 +1,34 @@ -using NUnit.Framework; +using System; +using System.Globalization; +using FluentAssertions; +using NUnit.Framework; -namespace ObjectPrinting.Tests +namespace ObjectPrinting.Tests; + +[TestFixture] +public class ObjectPrinterAcceptanceTests { - [TestFixture] - public class ObjectPrinterAcceptanceTests + [Test] + public void Demo() { - [Test] - public void Demo() - { - var person = new Person { Name = "Alex", Age = 19 }; + var person = new Person { Name = "Alex", Age = 19 }; - var printer = ObjectPrinter.For(); - //1. Исключить из сериализации свойства определенного типа - //2. Указать альтернативный способ сериализации для определенного типа - //3. Для числовых типов указать культуру - //4. Настроить сериализацию конкретного свойства - //5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств) - //6. Исключить из сериализации конкретного свойства + var printer = ObjectPrinter.For() + .For().ExcludeType().End() + .For().SetSerializer(x=>$"int{x}int").End() + .For().UsingCulture(new CultureInfo("en-US")).End() + .For(x=>x.Id).SetSerializer(x=>$"id{x}").End() + .For(x=>x.Name).TrimToLength(2).End() + .For(x=>x.Description).ExcludeProperty().End(); + //1. Исключить из сериализации свойства определенного типа + //2. Указать альтернативный способ сериализации для определенного типа + //3. Для числовых типов указать культуру + //4. Настроить сериализацию конкретного свойства + //5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств) + //6. Исключить из сериализации конкретного свойства - string s1 = printer.PrintToString(person); - - //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию - //8. ...с конфигурированием - } + string s1 = printer.PrintToString(person); + //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию + //8. ...с конфигурированием } } \ No newline at end of file diff --git a/ObjectPrinting/Tests/Person.cs b/ObjectPrinting/Tests/Person.cs index f95559554..788b3c7f2 100644 --- a/ObjectPrinting/Tests/Person.cs +++ b/ObjectPrinting/Tests/Person.cs @@ -1,12 +1,13 @@ using System; -namespace ObjectPrinting.Tests +namespace ObjectPrinting.Tests; + +public class Person { - public class Person - { - public Guid Id { get; set; } - public string Name { get; set; } - public double Height { get; set; } - public int Age { get; set; } - } + public Guid Id { get; set; } + public string Name { get; set; } + public int Age { get; set; } + public double Salary { get; set; } + public DateTime BirthDate { get; set; } + public string Description { get; set; } } \ No newline at end of file diff --git a/ObjectPrinting/Tests/PersonGroup.cs b/ObjectPrinting/Tests/PersonGroup.cs new file mode 100644 index 000000000..820ac72ae --- /dev/null +++ b/ObjectPrinting/Tests/PersonGroup.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace ObjectPrinting.Tests; + +public class PersonGroup +{ + public string GroupName { get; set; } + public Person Leader { get; set; } + public Person[] Members { get; set; } + public int[] Scores { get; set; } + public List ScoresList { get; set; } + public List> ScoresLists { get; set; } + public Dictionary> ScoresDictionary { get; set; } +} \ No newline at end of file diff --git a/ObjectPrinting/Tests/PrintingConfigTests.cs b/ObjectPrinting/Tests/PrintingConfigTests.cs new file mode 100644 index 000000000..c4a0388e2 --- /dev/null +++ b/ObjectPrinting/Tests/PrintingConfigTests.cs @@ -0,0 +1,386 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using FluentAssertions; +using NUnit.Framework; + +namespace ObjectPrinting.Tests; + +[TestFixture] +public class PrintingConfigTests +{ + private Person CreatePerson(string name = "Alexander", int age = 30) + { + return new Person + { + Id = Guid.Empty, + Name = name, + Salary = 1234.5, + Age = age, + BirthDate = new DateTime(2000, 1, 2), + Description = "Some long description" + }; + } + + private PersonGroup CreateGroup() + { + return new PersonGroup + { + GroupName = "DevTeam", + Leader = CreatePerson("Leader", 30), + Members = + [ + CreatePerson("Alice", 25), + CreatePerson("Bob", 28) + ], + Scores = [322, 67, 35], + ScoresList = [3.0, 4.2, 6.1], + ScoresLists = [[3.0, 4.2, 6.1], [3.0, 4.2, 6.1]], + ScoresDictionary = new Dictionary> + { + ["User1"] = new() + { + ["Score"] = 10, + ["Level"] = 1 + }, + ["User2"] = new() + { + ["Score"] = 5, + ["Level"] = 2 + } + } + }; + } + + [Test] + public void ExcludeType_Remove_PropertiesType() + { + var person = CreatePerson(); + var config = new PrintingConfig(); + + config.For() + .ExcludeType() + .End(); + + var result = config.PrintToString(person); + + result.Should().NotContain("Age ="); + result.Should().NotContain("30"); + result.Should().Contain("Name = Alexander"); + result.Should().Contain("Salary = 1234,5"); + } + + [Test] + public void SetSerializer_SetSerialization_ForType() + { + var person = CreatePerson(); + var config = new PrintingConfig(); + + config.For() + .SetSerializer(i => $"str:{i}") + .End(); + + var result = config.PrintToString(person); + + result.Should().Contain("Name = str:Alexander"); + result.Should().Contain("Description = str:Some long description"); + } + + [Test] + public void UsingCulture_ChangeCulture_ForPropertiesType() + { + var person = CreatePerson(); + var config = new PrintingConfig(); + + config.For() + .UsingCulture(new CultureInfo("en-US")) + .End(); + + var result = config.PrintToString(person); + + result.Should().Contain("Salary = 1234.5"); + } + + [Test] + public void PropertySetSerializer_SetSerialization_ForProperty() + { + var person = CreatePerson(); + var config = new PrintingConfig(); + + config.For(p => p.Age) + .SetSerializer(a => $"AgeIs:{a}") + .End(); + + var result = config.PrintToString(person); + + result.Should().Contain("Age = AgeIs:30"); + result.Should().Contain("Description = Some long description"); + } + + [Test] + public void PropertyExclude_Remove_Properties() + { + var person = CreatePerson(); + var config = new PrintingConfig(); + + config.For(p => p.Description) + .ExcludeProperty() + .End(); + + var result = config.PrintToString(person); + + result.Should().NotContain("Description ="); + result.Should().Contain("Name = Alexander"); + } + + [Test] + public void StringTrimToLength_Trim_StringProperty() + { + var person = CreatePerson(); + var config = new PrintingConfig(); + + config.For(p => p.Name) + .TrimToLength(5) + .End(); + + var result = config.PrintToString(person); + + result.Should().Contain("Name = Alexa"); + result.Should().NotContain("Alexander"); + } + + [Test] public void StringTrimToLengthAndSetSerializerShould_BothApplied() + { + var person = CreatePerson(); + var config = new PrintingConfig(); + + config.For(p => p.Name) + .TrimToLength(5) + .SetSerializer(s => s.ToUpperInvariant()) + .End(); + + var result = config.PrintToString(person); + + result.Should().Contain("Name = ALEXA"); + } + + [Test] + public void ExcludeType_HavePriority_OverPropertySetSerializer() + { + var person = CreatePerson(); + var config = new PrintingConfig(); + + config.For(p => p.Age) + .SetSerializer(a => $"AgeIs:{a}") + .End(); + + config.For() + .ExcludeType() + .End(); + + var result = config.PrintToString(person); + + result.Should().NotContain("Age ="); + result.Should().NotContain("AgeIs:30"); + } + + [Test] + public void Collections_SerializeCorrectly() + { + var group = CreateGroup(); + var config = new PrintingConfig(); + + var result = config.PrintToString(group); + + result.Should().Contain("PersonGroup"); + result.Should().Contain("GroupName = DevTeam"); + + result.Should().Contain("Members = Person[]"); + result.Should().Contain("Name = Alice"); + result.Should().Contain("Name = Bob"); + + result.Should().Contain("Scores = Int32[]"); + result.Should().Contain("322"); + result.Should().Contain("67"); + result.Should().Contain("35"); + + result.Should().Contain("ScoresList = List`1"); + result.Should().Contain("3"); + result.Should().Contain("4,2"); + result.Should().Contain("6,1"); + + result.Should().Contain("ScoresDictionary = Dictionary`2"); + result.Should().Contain("Key = User1"); + result.Should().Contain("Score"); + result.Should().Contain("10"); + result.Should().Contain("Key = User2"); + result.Should().Contain("5"); + result.Should().Contain("Key = User1, Value = Dictionary`2"); + result.Should().Contain("Key = Score, Value = 10"); + } + + [Test] + public void ExcludeType_WorkInsideCollections() + { + var group = CreateGroup(); + var config = new PrintingConfig(); + + config.For() + .ExcludeType() + .End(); + + var result = config.PrintToString(group); + + result.Should().NotContain("Age ="); + result.Should().Contain("Scores = Int32[]"); + result.Should().NotContain("322"); + result.Should().NotContain("67"); + result.Should().NotContain("35"); + } + + [Test] + public void PropertySetSerializer_Work_ForNestedProperty() + { + var group = CreateGroup(); + var config = new PrintingConfig(); + + config.For(g => g.Leader.Name) + .SetSerializer(s => $"LEADER:{s}") + .End(); + + var result = config.PrintToString(group); + + result.Should().Contain("Name = LEADER:Leader"); + result.Should().Contain("Name = Alice"); + result.Should().Contain("Name = Bob"); + } + + [Test] + public void TypeSetSerializer_Work_InsideCollections() + { + var group = CreateGroup(); + var config = new PrintingConfig(); + + config.For() + .SetSerializer(i => $"INT:{i}") + .End(); + + var result = config.PrintToString(group); + + result.Should().Contain("Age = INT:30"); + result.Should().Contain("Age = INT:25"); + result.Should().Contain("Age = INT:28"); + result.Should().Contain("Scores = Int32[]"); + result.Should().Contain("INT:322"); + result.Should().Contain("INT:67"); + result.Should().Contain("INT:35"); + result.Should().Contain("ScoresDictionary = Dictionary`2"); + result.Should().Contain("INT:10"); + result.Should().Contain("INT:1"); + result.Should().Contain("INT:5"); + result.Should().Contain("INT:2"); + } + + [Test] + public void UsingCulture_Work_InsideCollections() + { + var group = CreateGroup(); + var config = new PrintingConfig(); + + config.For() + .UsingCulture(new CultureInfo("en-US")) + .End(); + + var result = config.PrintToString(group); + + result.Should().Contain("Salary = 1234.5"); + result.Should().Contain("ScoresList = List`1"); + result.Should().Contain("3"); + result.Should().Contain("4.2"); + result.Should().Contain("6.1"); + } + + [Test] + public void StringTrimToLength_Work_InsideCollections() + { + var group = CreateGroup(); + var config = new PrintingConfig(); + + config.For(p => p.Leader.Name) + .TrimToLength(1) + .End(); + + var result = config.PrintToString(group); + + result.Should().Contain("Name = L"); + result.Should().Contain("Name = A"); + result.Should().Contain("Name = B"); + } + + [Test] + public void ExcludeCollections_NotSerializeCollectionsWithElements() + { + var group = CreateGroup(); + var config = new PrintingConfig(); + + config.For() + .ExcludeType() + .End(); + + var result = config.PrintToString(group); + + result.Should().NotContain("Scores = Int32[]"); + result.Should().NotContain("322"); + result.Should().NotContain("67"); + result.Should().NotContain("35"); + } + + [Test] + public void ExcludeProperty_Work_ForNestedProperty() + { + var group = CreateGroup(); + var config = new PrintingConfig(); + + config.For(x=>x.Leader.Age) + .ExcludeProperty() + .End(); + + var result = config.PrintToString(group); + + result.Should().NotContain("Age = 30"); + result.Should().Contain("Age = 25"); + result.Should().Contain("Age = 28"); + } + [Test] + public void CircularDependency_NotCauseStackOverflow() + { + var list = new List(); + list.Add("first"); + list.Add(list); + + var config = new PrintingConfig>(); + var result = config.PrintToString(list); + + result.Should().Contain("List`1"); + result.Should().Contain("first"); + result.Should().Contain("circular dependency"); + } + + [Test] + public void CollectionTypeSetSerializer_Work_ForConcreteCollectionType() + { + var group = CreateGroup(); + var config = new PrintingConfig(); + + config.For>() + .SetSerializer(list => + $"List (Count = {list.Count}): [{string.Join(", ", list)}]") + .End(); + + var result = config.PrintToString(group); + + result.Should().Contain("ScoresList = List (Count = 3): [3, 4,2, 6,1]"); + result.Should().Contain("ScoresLists = List`1"); + result.Should().Contain("List (Count = 3): [3, 4,2, 6,1]"); + } +} \ No newline at end of file