diff --git a/ObjectPrinting/ObjectPrinter.cs b/ObjectPrinting/ObjectPrinter.cs index 3c7867c3..a70c6ffb 100644 --- a/ObjectPrinting/ObjectPrinter.cs +++ b/ObjectPrinting/ObjectPrinter.cs @@ -1,3 +1,6 @@ +using System; +using System.Text; + namespace ObjectPrinting { public class ObjectPrinter @@ -7,4 +10,12 @@ public static PrintingConfig For() return new PrintingConfig(); } } + + public static class ObjectPrinterExtensions + { + public static string PrintToString(this T obj) + { + return ObjectPrinter.For().PrintToString(obj); + } + } } \ No newline at end of file diff --git a/ObjectPrinting/ObjectPrinting.csproj b/ObjectPrinting/ObjectPrinting.csproj index 1c5eaf1c..635d8ebb 100644 --- a/ObjectPrinting/ObjectPrinting.csproj +++ b/ObjectPrinting/ObjectPrinting.csproj @@ -4,11 +4,14 @@ 8 netcoreapp3.1 false + AutoGeneratedProgram + + diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index a9e08211..8225de33 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -1,39 +1,139 @@ using System; +using System.Collections; +using System.Collections.Generic; +using System.Data; using System.Linq; +using System.Linq.Expressions; +using System.Reflection; using System.Text; namespace ObjectPrinting { public class PrintingConfig { + private readonly HashSet excludedProperties = new HashSet(); + private readonly HashSet excludedTypes = new HashSet(); + + internal readonly Dictionary> TypeSerializers = new Dictionary>(); + internal readonly Dictionary> PropertySerializers = new Dictionary>(); + + private readonly HashSet serializedObjects = new HashSet(); + + public PrintingConfig Exclude() + { + excludedTypes.Add(typeof(TProp)); + return this; + } + public PrintingConfig Exclude(Expression> exclude) + { + var propertyInfo = ((MemberExpression)exclude.Body).Member as PropertyInfo; + excludedProperties.Add(propertyInfo); + return this; + } + + public TypePrintingConfig Serialize() + { + return new TypePrintingConfig(this); + } + + public PropertyPrintingConfig Serialize(Expression> customSerialize) + { + var propertyInfo = ((MemberExpression)customSerialize.Body).Member as PropertyInfo; + return new PropertyPrintingConfig(this, propertyInfo); + } + public string PrintToString(TOwner obj) { return PrintToString(obj, 0); } + private string DictionarySerialize(PropertyInfo propertyInfo, object obj) + { + var dictionaryValue = propertyInfo.GetValue(obj) as IDictionary; + if (dictionaryValue == null) + { + return string.Empty; + } + + var keys = dictionaryValue.Keys.OfType().Select(item => item.ToString()).ToArray(); + var values = dictionaryValue.Values.OfType().Select(item => item.ToString()).ToArray(); + var pair = keys.Zip(values).Select(pair => string.Format("({0}, {1})", pair.First, pair.Second)); + + return $"{propertyInfo.Name} = {{ " + string.Join(", ", pair) + " }"; + } + + private string IEnumerableSerialize(PropertyInfo propertyInfo, object obj) + { + var listValue = propertyInfo.GetValue(obj) as IEnumerable; + if (listValue == null) + { + return string.Empty; + } + + var items = listValue.OfType().Select(item => item.ToString()).ToArray(); + return $"{propertyInfo.Name} = {{ " + string.Join(", ", items) + " }"; + } + 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; + if (serializedObjects.Contains(obj)) + return obj.GetType().GetTypeInfo().Name + Environment.NewLine; + + serializedObjects.Add(obj); var identation = new string('\t', nestingLevel + 1); + + if(nestingLevel > 5) + return identation; + var sb = new StringBuilder(); var type = obj.GetType(); sb.AppendLine(type.Name); + + if (type.IsValueType || obj is string) + { + if (TypeSerializers.ContainsKey(type)) + { + + return TypeSerializers[type](obj) + Environment.NewLine; + } + return obj + Environment.NewLine; + } + + if (type.IsValueType && TypeSerializers.ContainsKey(type)) + { + sb.Append(TypeSerializers[type](obj) + Environment.NewLine); + } + foreach (var propertyInfo in type.GetProperties()) { - sb.Append(identation + propertyInfo.Name + " = " + - PrintToString(propertyInfo.GetValue(obj), - nestingLevel + 1)); + if (excludedProperties.Contains(propertyInfo)) + continue; + if (excludedTypes.Contains(propertyInfo.PropertyType)) + continue; + + if (PropertySerializers.ContainsKey(propertyInfo)) + { + sb.Append(identation + propertyInfo.Name + " = " + PropertySerializers[propertyInfo](propertyInfo.GetValue(obj)) + Environment.NewLine); + } + else if (propertyInfo.PropertyType.IsArray + || (propertyInfo.PropertyType.IsGenericType + && propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(List<>))) + { + sb.Append(identation + IEnumerableSerialize(propertyInfo, obj) + Environment.NewLine); + } + else if (propertyInfo.PropertyType.IsGenericType + && propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(Dictionary<,>)) + { + sb.Append(identation + DictionarySerialize(propertyInfo, obj) + Environment.NewLine); + } + else + { + sb.Append(identation + propertyInfo.Name + " = " + PrintToString(propertyInfo.GetValue(obj), nestingLevel + 1)); + } } return sb.ToString(); } diff --git a/ObjectPrinting/PropertyPrintingConfig.cs b/ObjectPrinting/PropertyPrintingConfig.cs new file mode 100644 index 00000000..9b9dc395 --- /dev/null +++ b/ObjectPrinting/PropertyPrintingConfig.cs @@ -0,0 +1,35 @@ +using ObjectPrinting; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; + +namespace ObjectPrinting +{ + public class PropertyPrintingConfig : IPropertyPrintingConfig + { + public readonly PrintingConfig printingConfig; + public readonly PropertyInfo propertyInfo; + PrintingConfig IPropertyPrintingConfig.ParentConfig => printingConfig; + + public PropertyPrintingConfig(PrintingConfig printingConfig, PropertyInfo propertyInfo) + { + this.printingConfig = printingConfig; + this.propertyInfo = propertyInfo; + } + + public PrintingConfig Using(Func print) + { + var func = new Func(obj => print((TPropType)obj)); + printingConfig.PropertySerializers.Add(propertyInfo, func); + return printingConfig; + } + } + public interface IPropertyPrintingConfig + { + PrintingConfig ParentConfig { get; } + } +} diff --git a/ObjectPrinting/PropertyPrintingConfigExtensions.cs b/ObjectPrinting/PropertyPrintingConfigExtensions.cs new file mode 100644 index 00000000..e56d7976 --- /dev/null +++ b/ObjectPrinting/PropertyPrintingConfigExtensions.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace ObjectPrinting +{ + public static class PropertyPrintingConfigExtensions + { + public static PrintingConfig Trim(this PropertyPrintingConfig propertyPrintingConfig, int length) + { + var cultureFunc = new Func(s => s.Substring(0, Math.Min(length, s.Length))); + var func = new Func(obj => cultureFunc((string)obj)); + var prop = propertyPrintingConfig.printingConfig; + prop.PropertySerializers.Add(propertyPrintingConfig.propertyInfo, func); + + return prop; + } + } +} diff --git a/ObjectPrinting/Tests/IEnums.cs b/ObjectPrinting/Tests/IEnums.cs new file mode 100644 index 00000000..1539ab50 --- /dev/null +++ b/ObjectPrinting/Tests/IEnums.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace ObjectPrinting.Tests +{ + public class IEnums + { + public int[] Array { get; set; } + public List List { get; set; } + public Dictionary Dict { get; set; } + } +} diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs index 4c8b2445..f4209fdc 100644 --- a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs +++ b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs @@ -1,27 +1,114 @@ using NUnit.Framework; +using FluentAssertions; +using System; +using System.Globalization; +using System.Collections.Generic; namespace ObjectPrinting.Tests { [TestFixture] public class ObjectPrinterAcceptanceTests { + Person person = new Person(); + + [SetUp] + public void Setup() + { + person = new Person { Name = "Alex", Age = 19, Height = 12.34, Id = new Guid() }; + } + + [Test] + public void PrintToString_SkipExcludedPropertys() + { + var printer = ObjectPrinter.For().Exclude(x=>x.Age); + var serialized = printer.PrintToString(person); + serialized.Should().Be("Person\r\n\tId = 00000000-0000-0000-0000-000000000000\r\n\tName = Alex\r\n\tHeight = 12,34\r\n\tparent = null\r\n"); + } + + [Test] + public void PrintToString_SkipExcludedTypes() + { + var printer = ObjectPrinter.For().Exclude(); + var serialized = printer.PrintToString(person); + serialized.Should().Be("Person\r\n\tId = 00000000-0000-0000-0000-000000000000\r\n\tName = Alex\r\n\tHeight = 12,34\r\n\tparent = null\r\n"); + } + + [Test] + public void PrintToString_UseCustomSerializerForCertainType() + { + var printer = ObjectPrinter.For().Serialize().Using(x=>x.ToString("f0")); + var serialized = printer.PrintToString(person); + serialized.Should().Be("Person\r\n\tId = 00000000-0000-0000-0000-000000000000\r\n\tName = Alex\r\n\tHeight = 12\r\n\tAge = 19\r\n\tparent = null\r\n"); + } + [Test] - public void Demo() + public void PrintToString_UseCustomSerializerForCertainProperty() { - var person = new Person { Name = "Alex", Age = 19 }; + var printer = ObjectPrinter.For().Serialize(x=>x.Name).Using(x=>"Property Serializer"); + var serialized = printer.PrintToString(person); + serialized.Should().Be("Person\r\n\tId = 00000000-0000-0000-0000-000000000000\r\n\tName = Property Serializer\r\n\tHeight = 12,34\r\n\tAge = 19\r\n\tparent = null\r\n"); + } + [Test] + public void PrintToString_UseCustomCultureForCertainProperty() + { + var printer = ObjectPrinter.For().Serialize().Using(CultureInfo.InvariantCulture); + var serialized = printer.PrintToString(person); + serialized.Should().Be("Person\r\n\tId = 00000000-0000-0000-0000-000000000000\r\n\tName = Alex\r\n\tHeight = 12.34\r\n\tAge = 19\r\n\tparent = null\r\n"); + } + + [Test] + public void PrintToString_TrimLimitStringLength() + { + var printer = ObjectPrinter.For().Serialize(x=>x.Name).Trim(1); + var serialized = printer.PrintToString(person); + serialized.Should().Be("Person\r\n\tId = 00000000-0000-0000-0000-000000000000\r\n\tName = A\r\n\tHeight = 12,34\r\n\tAge = 19\r\n\tparent = null\r\n"); + } + + + [Test] + public void PrintToString_ParsinCyclingLinks() + { + var parent = new Person(); + parent.Id = new Guid("00000000-0000-0000-0000-012147483648"); + person.parent = parent; + parent.parent = person; var printer = ObjectPrinter.For(); - //1. Исключить из сериализации свойства определенного типа - //2. Указать альтернативный способ сериализации для определенного типа - //3. Для числовых типов указать культуру - //4. Настроить сериализацию конкретного свойства - //5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств) - //6. Исключить из сериализации конкретного свойства - - string s1 = printer.PrintToString(person); - - //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию - //8. ...с конфигурированием + var serialized = printer.PrintToString(person); + serialized.Should().Be("Person\r\n\tId = 00000000-0000-0000-0000-000000000000\r\n\tName = Alex\r\n\tHeight = 12,34\r\n\tAge = 19\r\n\tparent = Person\r\n\t\tId = 00000000-0000-0000-0000-012147483648\r\n\t\tName = null\r\n\t\tHeight = 0\r\n\t\tAge = 0\r\n\t\tparent = Person\r\n"); + } + + [Test] + public void PrintToString_ParsinArrays() + { + var arr = new IEnums(); + arr.Array = new int[] { 1, 2, 3 }; + + var printer = ObjectPrinter.For(); + var serialized = printer.PrintToString(arr); + serialized.Should().Be("IEnums\r\n\tArray = { 1, 2, 3 }\r\n\t\r\n\t\r\n"); + } + + [Test] + public void PrintToString_ParsinLists() + { + var list = new IEnums(); + list.List = new List() {1,2,3 }; + + var printer = ObjectPrinter.For(); + var serialized = printer.PrintToString(list); + serialized.Should().Be("IEnums\r\n\t\r\n\tList = { 1, 2, 3 }\r\n\t\r\n"); + } + + [Test] + public void PrintToString_ParsinDicts() + { + var dict = new IEnums(); + dict.Dict = new Dictionary() { {'a',1 },{'b',2 },{'c',3 } }; + + var printer = ObjectPrinter.For(); + var serialized = printer.PrintToString(dict); + serialized.Should().Be("IEnums\r\n\t\r\n\t\r\n\tDict = { (a, 1), (b, 2), (c, 3) }\r\n"); } } } \ No newline at end of file diff --git a/ObjectPrinting/Tests/Person.cs b/ObjectPrinting/Tests/Person.cs index f9555955..cc341874 100644 --- a/ObjectPrinting/Tests/Person.cs +++ b/ObjectPrinting/Tests/Person.cs @@ -1,12 +1,10 @@ using System; -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 double Height { get; set; } + public int Age { get; set; } + public Person parent { get; set; } } \ No newline at end of file diff --git a/ObjectPrinting/TypePrintingConfig.cs b/ObjectPrinting/TypePrintingConfig.cs new file mode 100644 index 00000000..cfad1fab --- /dev/null +++ b/ObjectPrinting/TypePrintingConfig.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Reflection; +using System.Text; + +namespace ObjectPrinting +{ + public class TypePrintingConfig : IPropertyPrintingConfig + { + private readonly PrintingConfig printingConfig; + PrintingConfig IPropertyPrintingConfig.ParentConfig => printingConfig; + + public TypePrintingConfig(PrintingConfig printingConfig) + { + this.printingConfig = printingConfig; + } + + public PrintingConfig Using(Func print) + { + var func = new Func(obj => print((TPropType)obj)); + printingConfig.TypeSerializers.Add(typeof(TPropType), func); + return printingConfig; + } + + public PrintingConfig Using(CultureInfo culture) where T:IFormattable + { + var cultureFunc = new Func(x=>x.ToString(null,culture)); + var func = new Func(obj => cultureFunc((T)obj)); + printingConfig.TypeSerializers.Add(typeof(TPropType), func); + return printingConfig; + } + } +}