diff --git a/ObjectPrinting/EnumerablePrinter.cs b/ObjectPrinting/EnumerablePrinter.cs new file mode 100644 index 000000000..dc9aff465 --- /dev/null +++ b/ObjectPrinting/EnumerablePrinter.cs @@ -0,0 +1,57 @@ +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 + { + var i = 0; + foreach (var item in enumerable) + { + sb.Append(indent + "\t"); + + 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)}"); + } + else + { + sb.Append($"[{i}] = {serializer.PrintInternal(item, level + 1, config, visited)}"); + } + + i++; + } + } + + return sb.ToString(); + } +} \ No newline at end of file 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/IPrintingConfigInternal.cs b/ObjectPrinting/Interface/IPrintingConfigInternal.cs new file mode 100644 index 000000000..10e275e66 --- /dev/null +++ b/ObjectPrinting/Interface/IPrintingConfigInternal.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; + +namespace ObjectPrinting.Interface; + +internal interface IPrintingConfigInternal +{ + HashSet ExcludedTypes { get; } + HashSet ExcludedProperties { get; } + + Dictionary> TypeSerializers { get; } + Dictionary> PropertySerializers { get; } + Dictionary TrimLengths { get; } + public int? GlobalStringTrimLength { get; } +} 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/MemberPrinter.cs b/ObjectPrinting/MemberPrinter.cs new file mode 100644 index 000000000..93a5fdd9c --- /dev/null +++ b/ObjectPrinting/MemberPrinter.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +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 propTrim)) + maxLen = propTrim; + 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/MemberPrintingConfig.cs b/ObjectPrinting/MemberPrintingConfig.cs new file mode 100644 index 000000000..02a103fc6 --- /dev/null +++ b/ObjectPrinting/MemberPrintingConfig.cs @@ -0,0 +1,30 @@ +using System; +using System.Globalization; +using ObjectPrinting.Interface; + +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 IStringMemberConfig AsString() + { + return typeof(TProp) == typeof(string) + ? new StringMemberConfig(config, memberName) + : throw new InvalidOperationException("Property is not string"); + } +} diff --git a/ObjectPrinting/ObjectPrinterExtensions.cs b/ObjectPrinting/ObjectPrinterExtensions.cs new file mode 100644 index 000000000..0b5342eb5 --- /dev/null +++ b/ObjectPrinting/ObjectPrinterExtensions.cs @@ -0,0 +1,13 @@ +using System; + +namespace ObjectPrinting; + +public static class ObjectPrinterExtensions +{ + 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/ObjectSerializer.cs b/ObjectPrinting/ObjectSerializer.cs new file mode 100644 index 000000000..fca6dcd35 --- /dev/null +++ b/ObjectPrinting/ObjectSerializer.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using System.Text; +using ObjectPrinting.Interface; + +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); + } + + internal string PrintInternal( + object obj, + int level, + IPrintingConfigInternal config, + HashSet visited) + { + if (obj == null) + return "null" + Environment.NewLine; + + var type = obj.GetType(); + + if (TypeHelper.IsSimpleType(type)) + return obj + Environment.NewLine; + + if (!type.IsValueType && !visited.Add(obj)) + return $"" + Environment.NewLine; + + if (obj is IEnumerable enumerable) + return enumerablePrinter.PrintEnumerable(enumerable, type, level, config, visited, this); + + var indent = new string('\t', level); + var sb = new StringBuilder(); + + sb.AppendLine(type.Name); + + 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); + sb.Append(indent + "\t" + + memberPrinter.PrintMember(property.Name, value, property.PropertyType, config, level + 1, visited, this)); + } + + 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); + sb.Append(indent + "\t" + + memberPrinter.PrintMember(field.Name, value, field.FieldType, config, level + 1, visited, this)); + } + + return sb.ToString(); + } +} diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index a9e082117..e897d8f67 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -1,41 +1,63 @@ using System; -using System.Linq; -using System.Text; +using System.Collections.Generic; +using System.Globalization; +using System.Linq.Expressions; +using ObjectPrinting.Interface; -namespace ObjectPrinting +namespace ObjectPrinting; + +public class PrintingConfig : IPrintingConfigInternal { - public class PrintingConfig + public HashSet ExcludedTypes { get; } = new(); + public HashSet ExcludedProperties { get; } = new(); + + public Dictionary> TypeSerializers { get; } = new(); + public Dictionary> PropertySerializers { get; } = new(); + public Dictionary TrimLengths { get; } = new(); + public int? GlobalStringTrimLength { get; private set; } + + private readonly ObjectSerializer objectSerializer = new ObjectSerializer(); + + public PrintingConfig TrimStringsGlobal(int maxLength) + { + GlobalStringTrimLength = maxLength; + return this; + } + + public PrintingConfig Excluding() + { + ExcludedTypes.Add(typeof(TProp)); + return this; + } + + public PrintingConfig Excluding(Expression> selector) + { + var name = ((MemberExpression)selector.Body).Member.Name; + ExcludedProperties.Add(name); + return this; + } + + 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) + { + var name = ((MemberExpression)selector.Body).Member.Name; + return new MemberPrintingConfig(this, name); + } + + public string PrintToString(TOwner obj) { - 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(); - } + return objectSerializer.Print(obj, this); } -} \ 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/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 deleted file mode 100644 index 540ee769c..000000000 --- a/ObjectPrinting/Solved/ObjectPrinter.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace ObjectPrinting.Solved -{ - public class ObjectPrinter - { - public static PrintingConfig For() - { - return new PrintingConfig(); - } - } -} \ No newline at end of file diff --git a/ObjectPrinting/Solved/PrintingConfig.cs b/ObjectPrinting/Solved/PrintingConfig.cs deleted file mode 100644 index 0ec5aeb2b..000000000 --- a/ObjectPrinting/Solved/PrintingConfig.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Linq; -using System.Linq.Expressions; -using System.Text; - -namespace ObjectPrinting.Solved -{ - public class PrintingConfig - { - public PropertyPrintingConfig Printing() - { - return new PropertyPrintingConfig(this); - } - - public PropertyPrintingConfig Printing(Expression> memberSelector) - { - return new PropertyPrintingConfig(this); - } - - public PrintingConfig Excluding(Expression> memberSelector) - { - return this; - } - - internal PrintingConfig Excluding() - { - return this; - } - - public string PrintToString(TOwner obj) - { - return PrintToString(obj, 0); - } - - private string PrintToString(object obj, int nestingLevel) - { - //TODO apply configurations - if (obj == null) - return "null" + Environment.NewLine; - - var finalTypes = new[] - { - typeof(int), typeof(double), typeof(float), typeof(string), - typeof(DateTime), typeof(TimeSpan) - }; - if (finalTypes.Contains(obj.GetType())) - return obj + Environment.NewLine; - - var identation = new string('\t', nestingLevel + 1); - var sb = new StringBuilder(); - var type = obj.GetType(); - sb.AppendLine(type.Name); - foreach (var propertyInfo in type.GetProperties()) - { - sb.Append(identation + propertyInfo.Name + " = " + - PrintToString(propertyInfo.GetValue(obj), - nestingLevel + 1)); - } - return sb.ToString(); - } - } -} \ No newline at end of file diff --git a/ObjectPrinting/Solved/PropertyPrintingConfig.cs b/ObjectPrinting/Solved/PropertyPrintingConfig.cs 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/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/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/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/ObjectPrinterAcceptanceTests.cs b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs deleted file mode 100644 index 4c8b2445c..000000000 --- a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -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. Исключить из сериализации свойства определенного типа - //2. Указать альтернативный способ сериализации для определенного типа - //3. Для числовых типов указать культуру - //4. Настроить сериализацию конкретного свойства - //5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств) - //6. Исключить из сериализации конкретного свойства - - string s1 = printer.PrintToString(person); - - //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию - //8. ...с конфигурированием - } - } -} \ No newline at end of file diff --git a/ObjectPrinting/Tests/ObjectPrinterNestedTests.cs b/ObjectPrinting/Tests/ObjectPrinterNestedTests.cs new file mode 100644 index 000000000..0072bcd8f --- /dev/null +++ b/ObjectPrinting/Tests/ObjectPrinterNestedTests.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +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().Using(CultureInfo.InvariantCulture) + .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/ObjectPrinterTests.cs b/ObjectPrinting/Tests/ObjectPrinterTests.cs new file mode 100644 index 000000000..140a9f51e --- /dev/null +++ b/ObjectPrinting/Tests/ObjectPrinterTests.cs @@ -0,0 +1,174 @@ + +using System; +using System.Collections.Generic; +using System.Globalization; +using NUnit.Framework; + +namespace ObjectPrinting.Tests; + +public class ObjectPrinterTests +{ + [Test] + public void ExcludingTypeTest() + { + var person = new Person { Name = "Alex", Age = 25, Id = Guid.NewGuid() }; + var printer = ObjectPrinter.For() + .Excluding() + .Excluding(); + 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")); + } + + [Test] + public void ExcludingPropertyTest() + { + var person = new Person { Name = "Alex", Age = 25, Id = Guid.NewGuid() }; + var printer = ObjectPrinter.For() + .Excluding(p => p.Age); + var 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}"); + var 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).AsString().Trim(3); + var printed = printer.PrintToString(person); + Assert.That(printed, Contains.Substring("Name = Ale")); + } + + [Test] + public void Printing_WithCulture_ForIFormattableTypes() + { + 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); + + 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] + public void ExtensionWithConfig() + { + var person = new Person { Name = "Alex", Age = 25 }; + + var 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 DefaultSerializationIncludesPropertiesAndFields() + { + var person = new Person { FirstName = "Ivanov", Name = "Alex", Age = 25 }; + var printer = ObjectPrinter.For(); + var 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(); + var 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(); + var 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(); + var result = printer.PrintToString(obj); + + Assert.That(result, Contains.Substring("Dict")); + 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()) + ); + } + + [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 diff --git a/ObjectPrinting/Tests/Person.cs b/ObjectPrinting/Tests/Person.cs index f95559554..6c30924d8 100644 --- a/ObjectPrinting/Tests/Person.cs +++ b/ObjectPrinting/Tests/Person.cs @@ -1,12 +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 Guid Id { get; set; } - public string Name { get; set; } - public double Height { get; set; } - public int Age { get; set; } + Tags = new List(); } -} \ No newline at end of file +} + +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; } +} 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 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 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