diff --git a/ObjectPrinting/ISerializer.cs b/ObjectPrinting/ISerializer.cs new file mode 100644 index 00000000..767fd109 --- /dev/null +++ b/ObjectPrinting/ISerializer.cs @@ -0,0 +1,13 @@ +using System; +using System.Linq.Expressions; + +namespace ObjectPrinting +{ + public interface ISerializer + { + string PrintToString(TOwner obj); + PropertySetting SelectProperty

(Expression> properties); + ISerializer ChangeProperty(Func method); + ISerializer Exclude(); + } +} \ No newline at end of file diff --git a/ObjectPrinting/ObjectPrinting.csproj b/ObjectPrinting/ObjectPrinting.csproj index 1c5eaf1c..fb67b65b 100644 --- a/ObjectPrinting/ObjectPrinting.csproj +++ b/ObjectPrinting/ObjectPrinting.csproj @@ -1,7 +1,7 @@  - 8 + latest netcoreapp3.1 false diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index a9e08211..4b9e23b2 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -1,40 +1,147 @@ using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; using System.Linq; +using System.Linq.Expressions; using System.Text; + namespace ObjectPrinting { - public class PrintingConfig + public class PrintingConfig : ISerializer { + private readonly Dictionary> propertiesOptions = new(); + + private readonly Dictionary> stringOptions = new(); + + private readonly Dictionary> optionsTypes = new(); + + private readonly HashSet exceptTypes = new(); + + private readonly Type[] types = + { + typeof(int), + typeof(double), + typeof(float), + typeof(bool), + typeof(DateTime), + typeof(TimeSpan) + }; + + public string PrintToString(TOwner obj) { return PrintToString(obj, 0); } - private string PrintToString(object obj, int nestingLevel) + public PropertySetting SelectProperty

(Expression> properties) + { + var memberExpression = (MemberExpression)properties.Body; + var propertySetting = new PropertySetting(this); + propertiesOptions[memberExpression.Member.Name] = propertySetting; + + return propertySetting; + } + + public StringSetting SelectProperty(Expression> properties) + { + var memberExpression = (MemberExpression)properties.Body; + var stringSetting = new StringSetting(this); + stringOptions[memberExpression.Member.Name] = stringSetting; + propertiesOptions[memberExpression.Member.Name] = stringSetting; + + return stringSetting; + } + + public ISerializer ChangeProperty(Func method) + { + optionsTypes.Add(typeof(T2), method); + return this; + } + + public ISerializer Exclude() + { + exceptTypes.Add(typeof(T2)); + return this; + } + + public PrintingConfig Exclude(Expression> properties) + { + var memberExpression = (MemberExpression)properties.Body; + var propertySetting = new PropertySetting(this, true); + propertiesOptions[memberExpression.Member.Name] = propertySetting; + + return this; + } + + private string PrintToString(object obj, int id, string name = "") { - //TODO apply configurations if (obj == null) - return "null" + Environment.NewLine; + return "null"; + + if (types.Contains(obj.GetType()) || id > 1) + { + var cultureInfo = CultureInfo.CurrentCulture; + if (propertiesOptions.TryGetValue(name, out var option)) + cultureInfo = option.Culture; + + return string.Format(cultureInfo, "{0}", obj); + } - var finalTypes = new[] + if (obj is string s) { - typeof(int), typeof(double), typeof(float), typeof(string), - typeof(DateTime), typeof(TimeSpan) - }; - if (finalTypes.Contains(obj.GetType())) - return obj + Environment.NewLine; + var maxLength = -1; + if (stringOptions.TryGetValue(name, out var option)) + maxLength = option.MaxLength; + if (maxLength < 0) + return obj as string; + + return s[..maxLength]; + } - var identation = new string('\t', nestingLevel + 1); var sb = new StringBuilder(); var type = obj.GetType(); - sb.AppendLine(type.Name); + sb.Append(type.Name + ":"); foreach (var propertyInfo in type.GetProperties()) { - sb.Append(identation + propertyInfo.Name + " = " + - PrintToString(propertyInfo.GetValue(obj), - nestingLevel + 1)); + var variable = propertyInfo.GetValue(obj); + if (exceptTypes.Contains(propertyInfo.PropertyType) || + propertiesOptions.ContainsKey(propertyInfo.Name) && + propertiesOptions[propertyInfo.Name].IsExcept) + continue; + if (propertiesOptions.ContainsKey(propertyInfo.Name) && + propertiesOptions[propertyInfo.Name].OutputMethod != null) + { + sb.Append(propertyInfo.Name + " = " + + propertiesOptions[propertyInfo.Name].OutputMethod.Invoke(variable)); + } + else if (optionsTypes.TryGetValue(propertyInfo.PropertyType, out var optionsType)) + { + sb.Append(propertyInfo.Name + " = " + + optionsType.Invoke(variable)); + } + else if (variable is IList list) + { + for (var i = 0; i < list.Count; i++) + sb.Append("\n" + i + " = " + + PrintToString(list[i], id + 1, propertyInfo.Name)); + } + else if (variable is IDictionary dict) + { + foreach (var key in dict.Keys) + { + var value = dict[key]; + sb.Append($"\n{key} = {PrintToString(value, id + 1, name)}"); + } + } + else + { + sb.Append(propertyInfo.Name + " = " + + PrintToString(propertyInfo.GetValue(obj), id + 1, propertyInfo.Name)); + } } + return sb.ToString(); } } diff --git a/ObjectPrinting/PropertySetting.cs b/ObjectPrinting/PropertySetting.cs new file mode 100644 index 00000000..e3d7c4aa --- /dev/null +++ b/ObjectPrinting/PropertySetting.cs @@ -0,0 +1,32 @@ +using System; +using System.Globalization; + +namespace ObjectPrinting +{ + public class PropertySetting + { + protected internal bool IsExcept { get; } + protected internal Func OutputMethod { get; private set; } + protected PrintingConfig Config { get; private set; } + protected internal CultureInfo Culture { get; private set; } + + public PropertySetting(PrintingConfig config, bool isExcept = false) + { + IsExcept = isExcept; + Config = config; + Culture = CultureInfo.CurrentCulture; + } + + public PrintingConfig ChangeField(Func func) + { + OutputMethod = func; + return Config; + } + + public PrintingConfig ChangeCulture(CultureInfo culture) + { + Culture = culture; + return Config; + } + } +} \ No newline at end of file diff --git a/ObjectPrinting/StringSetting.cs b/ObjectPrinting/StringSetting.cs new file mode 100644 index 00000000..632e0484 --- /dev/null +++ b/ObjectPrinting/StringSetting.cs @@ -0,0 +1,18 @@ +namespace ObjectPrinting +{ + public class StringSetting : PropertySetting + { + protected internal int MaxLength { get; private set; } + + public StringSetting(PrintingConfig config, bool isExcept = false) : base(config, isExcept) + { + MaxLength = -1; + } + + public PrintingConfig SetMaxLength(int length) + { + MaxLength = length; + return Config; + } + } +} \ No newline at end of file diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs index 4c8b2445..13dd27f5 100644 --- a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs +++ b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs @@ -1,27 +1,37 @@ -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 +// using System; +// using System.Diagnostics.CodeAnalysis; +// using System.Globalization; +// using NUnit.Framework; +// using ObjectPrinting.Solved; +// +// namespace ObjectPrinting.Tests +// { +// [TestFixture] +// public class ObjectPrinterAcceptanceTests +// { +// [Test] +// public void Demo() +// { +// var person = new Person { Name = "Alex", Age = 19 }; +// +// var printer = ObjectPrinter.For(); +// printer +// //1. Исключить из сериализации свойства определенного типа +// .Exluding() +// //2. Указать альтернативный способ сериализации для определенного типа +// //3. Для числовых типов указать культуру +// .Printing().By(CultureInfo.CurrentCulture) +// //4. Настроить сериализацию конкретного свойства +// .Printing().By(s => s.ToString()) +// //5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств) +// +// //6. Исключить из сериализации конкретного свойства +// .Exluding(p = p.Age); +// +// string s1 = printer.PrintToString(person); +// +// //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию +// //8. ...с конфигурированием +// } +// } +// } \ No newline at end of file diff --git a/ObjectPrintingTests/CollectionsTests.cs b/ObjectPrintingTests/CollectionsTests.cs new file mode 100644 index 00000000..98f86b21 --- /dev/null +++ b/ObjectPrintingTests/CollectionsTests.cs @@ -0,0 +1,76 @@ +using ObjectPrinting; + +namespace ObjectPrintingTests; + +public class CollectionsTests +{ + private class TestDictionary + { + public Dictionary DictionaryProperty { get; set; } + } + + [Test] + public void PrintToString_WithDictionary() + { + var obj = new TestDictionary + { + DictionaryProperty = new Dictionary + { + { "One", 1 }, + { "Two", 2 }, + { "Three", 3 } + } + }; + + var printer = ObjectPrinter.For(); + var result = printer.PrintToString(obj); + + result.Should().Be("TestDictionary:" + + "\nOne = 1" + + "\nTwo = 2" + + "\nThree = 3"); + } + + private class TestArray + { + public int[] ArrayProperty { get; set; } + } + + [Test] + public void PrintToString_WithArray() + { + var obj = new TestArray + { + ArrayProperty = new[] { 10, 20, 30 } + }; + var printingConfig = new PrintingConfig(); + var result = printingConfig.PrintToString(obj); + + result.Should().Be("TestArray:" + + "\n0 = 10" + + "\n1 = 20" + + "\n2 = 30"); + } + + private class TestList + { + public List ListProperty { get; set; } + } + + [Test] + public void PrintToString_WithList() + { + var obj = new TestList + { + ListProperty = new List { "One", "Two", "Three" } + }; + + var printer = ObjectPrinter.For(); + var result = printer.PrintToString(obj); + + result.Should().Be("TestList:" + + "\n0 = One" + + "\n1 = Two" + + "\n2 = Three"); + } +} \ No newline at end of file diff --git a/ObjectPrintingTests/ObjectPrintingTests.cs b/ObjectPrintingTests/ObjectPrintingTests.cs new file mode 100644 index 00000000..7002accd --- /dev/null +++ b/ObjectPrintingTests/ObjectPrintingTests.cs @@ -0,0 +1,135 @@ +using System.Globalization; +using ObjectPrinting; + + +namespace ObjectPrintingTests; + +[TestFixture] +public class Tests +{ + private Person person; + + [SetUp] + public void SetUp() + { + person = new Person + { + Id = new Guid(), + Name = "Nikita", + Height = 178, + Age = 20, + Birthday = new DateTime(2003, 5, 6), + Sex = true, + }; + } + + [Test] + public void PrintIsUnchanged() + { + var printer = ObjectPrinter.For(); + var s = printer.PrintToString(person); + s.Should().Be("Person:"+ + "Id = Guid:"+ + "Name = Nikita"+ + "Height = 178"+ + "Age = 20"+ + "Birthday = 06.05.2003 0:00:00"+ + "Sex = True"); + } + + [Test] + public void ExclusionFieldPropertySerialization() + { + var printer = ObjectPrinter.For(); + var s = printer + .Exclude(x => x.Name) + .Exclude(x => x.Height) + .PrintToString(person); + s.Should().Be("Person:"+ + "Id = Guid:"+ + "Age = 20"+ + "Birthday = 06.05.2003 0:00:00"+ + "Sex = True"); + } + + [Test] + public void ExclusionFromPropertySerialization() + { + var printer = ObjectPrinter.For(); + var s = printer + .Exclude() + .Exclude() + .PrintToString(person); + s.Should().Be("Person:"+ + "Id = Guid:" + + "Height = 178"+ + "Age = 20"+ + "Sex = True"); + } + + [Test] + public void ChangingSerializationField()//WithChangerOutputNameProperty() + { + var printer = ObjectPrinter.For(); + var s = printer + .SelectProperty(x => x.Name) + .ChangeField(_ => "Anton") + .PrintToString(person); + s.Should().Be("Person:" + + "Id = Guid:"+ + "Name = Anton"+ + "Height = 178"+ + "Age = 20"+ + "Birthday = 06.05.2003 0:00:00"+ + "Sex = True"); + } + + [Test] + public void ChangingSerializationProperty() + { + var printer = ObjectPrinter.For(); + var s = printer + .ChangeProperty(_ => "Egor") + .PrintToString(person); + s.Should().Be("Person:"+ + "Id = Guid:"+ + "Name = Egor"+ + "Height = 178"+ + "Age = 20"+ + "Birthday = 06.05.2003 0:00:00"+ + "Sex = True"); + } + + [Test] + public void TrimmedString() + { + var printer = ObjectPrinter.For(); + var s = printer.SelectProperty(x => x.Name) + .SetMaxLength(5) + .PrintToString(person); + s.Should().Be("Person:"+ + "Id = Guid:"+ + "Name = Nikit"+ + "Height = 178"+ + "Age = 20"+ + "Birthday = 06.05.2003 0:00:00"+ + "Sex = True"); + } + [Test] + public void ChangingCulture() + { + var printer = ObjectPrinter.For(); + var s = printer + .SelectProperty(x => x.Birthday) + .ChangeCulture(new CultureInfo("en-US")) + .PrintToString(person); + s.Should().Be("Person:"+ + "Id = Guid:"+ + "Name = Nikita"+ + "Height = 178"+ + "Age = 20"+ + "Birthday = 5/6/2003 12:00:00 AM"+ + "Sex = True"); + } + +} \ No newline at end of file diff --git a/ObjectPrintingTests/ObjectPrintingTests.csproj b/ObjectPrintingTests/ObjectPrintingTests.csproj new file mode 100644 index 00000000..1103f246 --- /dev/null +++ b/ObjectPrintingTests/ObjectPrintingTests.csproj @@ -0,0 +1,24 @@ + + + + net7.0 + enable + enable + + false + + + + + + + + + + + + + + + + diff --git a/ObjectPrintingTests/Person.cs b/ObjectPrintingTests/Person.cs new file mode 100644 index 00000000..af852a0f --- /dev/null +++ b/ObjectPrintingTests/Person.cs @@ -0,0 +1,11 @@ +namespace ObjectPrintingTests; + +public class Person +{ + public Guid Id { get; set; } + public string Name { get; set; } + public double Height { get; set; } + public int Age { get; set; } + public DateTime Birthday { get; set; } + public bool Sex { get; set; } +} \ No newline at end of file diff --git a/ObjectPrintingTests/Usings.cs b/ObjectPrintingTests/Usings.cs new file mode 100644 index 00000000..e0315ac9 --- /dev/null +++ b/ObjectPrintingTests/Usings.cs @@ -0,0 +1,2 @@ +global using NUnit.Framework; +global using FluentAssertions; diff --git a/fluent-api.sln b/fluent-api.sln index 69c8db9e..5b29bd7d 100644 --- a/fluent-api.sln +++ b/fluent-api.sln @@ -13,6 +13,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FluentMapping.Tests", "Samp EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Spectacle", "Samples\Spectacle\Spectacle.csproj", "{EFA9335C-411B-4597-B0B6-5438D1AE04C3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ObjectPrintingTests", "ObjectPrintingTests\ObjectPrintingTests.csproj", "{96B40C5C-1956-428A-9CD4-932B139D1B9B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -35,6 +37,10 @@ Global {EFA9335C-411B-4597-B0B6-5438D1AE04C3}.Debug|Any CPU.Build.0 = Debug|Any CPU {EFA9335C-411B-4597-B0B6-5438D1AE04C3}.Release|Any CPU.ActiveCfg = Release|Any CPU {EFA9335C-411B-4597-B0B6-5438D1AE04C3}.Release|Any CPU.Build.0 = Release|Any CPU + {96B40C5C-1956-428A-9CD4-932B139D1B9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {96B40C5C-1956-428A-9CD4-932B139D1B9B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {96B40C5C-1956-428A-9CD4-932B139D1B9B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {96B40C5C-1956-428A-9CD4-932B139D1B9B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE