diff --git a/ObjectPrinting.Test/Family.cs b/ObjectPrinting.Test/Family.cs new file mode 100644 index 00000000..c8a42600 --- /dev/null +++ b/ObjectPrinting.Test/Family.cs @@ -0,0 +1,8 @@ +namespace ObjectPrinting.Test; + +public record Family +{ + public Person? Mom { get; set; } + public Person? Dad { get; set; } + public List Children { get; set; } = []; +} \ No newline at end of file diff --git a/ObjectPrinting.Test/ObjectPrinterAcceptanceTests.cs b/ObjectPrinting.Test/ObjectPrinterAcceptanceTests.cs new file mode 100644 index 00000000..6320cb05 --- /dev/null +++ b/ObjectPrinting.Test/ObjectPrinterAcceptanceTests.cs @@ -0,0 +1,68 @@ +using System.Globalization; + +namespace ObjectPrinting.Test; + +[TestFixture] +public class ObjectPrinterAcceptanceTests +{ + [Test] + public void Demo() + { + var person = new Person + { + Name = "Alex", + Age = 19, + BestFriend = new Person + { + Name = "Bob", + Age = 40, + BestFriend = new Person() + }, + Friends = + [ + new Person + { + Name = "Alice", + Age = 19, + }, + new Person + { + Name = "Robert", + Age = 19, + }, + ], + BodyParts = + { + { "Hand", 2 }, + { "Foot", 2 }, + { "Head", 1 }, + { "Tail", 0 } + } + }; + + var printer = ObjectPrinter.For() + //1. Исключить из сериализации свойства определенного типа + .Exclude() + //2. Указать альтернативный способ сериализации для определенного типа + .PrintSettings().Using(i => i.ToString("X")) + //3. Для числовых типов указать культуру + .UseCulture(CultureInfo.InvariantCulture) + //4. Настроить сериализацию конкретного свойства + .PrintPropertySettings(x => x.Surname).Using(p => $"-{p}-") + //5. Настроить обрезание строковых свойств (метод должен быть виден только для строковых свойств) + .PrintPropertySettings(p => p.Name).TrimmedTo(2) + //6. Исключить из сериализации конкретного свойства + .Exclude(p => p.Age); + + var s1 = printer.PrintToString(person); + Console.WriteLine(s1); + + //7. Синтаксический сахар в виде метода расширения, сериализующего по-умолчанию + var s2 = person.PrintToString(); + Console.WriteLine(s2); + + //8. ...с конфигурированием + var s3 = person.PrintToString(p => p.Exclude(x => x.Name)); + Console.WriteLine(s3); + } +} \ No newline at end of file diff --git a/ObjectPrinting.Test/ObjectPrinting.Test.csproj b/ObjectPrinting.Test/ObjectPrinting.Test.csproj new file mode 100644 index 00000000..c936f20a --- /dev/null +++ b/ObjectPrinting.Test/ObjectPrinting.Test.csproj @@ -0,0 +1,29 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + + + diff --git a/ObjectPrinting.Test/Person.cs b/ObjectPrinting.Test/Person.cs new file mode 100644 index 00000000..aa0336c4 --- /dev/null +++ b/ObjectPrinting.Test/Person.cs @@ -0,0 +1,13 @@ +namespace ObjectPrinting.Test; + +public record Person +{ + public Guid Id { get; set; } + public string? Name { get; set; } + public string? Surname { get; set; } + public double Height { get; set; } + public int Age { get; set; } + public Person? BestFriend { get; set; } + public Person[] Friends { get; set; } = []; + public Dictionary BodyParts { get; set; } = new(); +} \ No newline at end of file diff --git a/ObjectPrinting.Test/PrintingConfigTests.cs b/ObjectPrinting.Test/PrintingConfigTests.cs new file mode 100644 index 00000000..df74958a --- /dev/null +++ b/ObjectPrinting.Test/PrintingConfigTests.cs @@ -0,0 +1,374 @@ +using System.Globalization; +using System.Text; +using FluentAssertions; + +namespace ObjectPrinting.Test; + +public class TestsObjectPrinting +{ + private Person firstPerson; + private Person secondPerson; + private Family family; + + [SetUp] + public void Setup() + { + firstPerson = new() + { + Name = "Ben", + Surname = "Big", + Height = 170.1, + Age = 20, + BestFriend = new() { Name = "Bob", Surname = "Boby", Height = 40, Age = 80 }, + Friends = + [ + new() { Name = "Alice", Surname = "Sev", Height = 50, Age = 30 }, + new() { Name = "Max", Surname = "Albor", Height = 10, Age = 9 } + ], + BodyParts = { { "Hand", 2 }, { "Foot", 2 }, { "Head", 1 }, { "Tail", 0 } } + }; + + secondPerson = new(); + + family = new() { Mom = firstPerson, Dad = secondPerson, Children = [firstPerson, secondPerson] }; + } + + [Test] + public void Exclude_ExcludeType() + { + const string unexpectedName = nameof(Person.Name); + const string unexpectedSurname = nameof(Person.Surname); + var result = ObjectPrinter.For() + .Exclude() + .PrintToString(firstPerson); + + result.Should().NotContain(unexpectedName).And.NotContain(unexpectedSurname); + } + + [Test] + public void Exclude_ExcludeProperty() + { + const string unexpectedAge = nameof(Person.Age); + var result = ObjectPrinter.For() + .Exclude(p => p.Age) + .PrintToString(firstPerson); + + result.Should().NotContain(unexpectedAge); + } + + [Test] + public void Exclude_AddSerializationToType_ThenExcludeType() + { + var unexpectedName = $"{firstPerson.Name} is beautiful"; + var unexpectedSurname = $"{firstPerson.Surname} is beautiful"; + var result = ObjectPrinter.For() + .PrintSettings().Using(p => $"{p} is beautiful") + .Exclude() + .PrintToString(firstPerson); + + result.Should().NotContain(unexpectedName).And.NotContain(unexpectedSurname); + } + + [Test] + public void Exclude_ExcludeAllProperties() + { + const string expected = "Person\r\n"; + var result = ObjectPrinter.For() + .Exclude(p => p.Id) + .Exclude(p => p.Name) + .Exclude(p => p.Surname) + .Exclude(p => p.Height) + .Exclude(p => p.Age) + .Exclude(p => p.BestFriend) + .Exclude(p => p.Friends) + .Exclude(p => p.BodyParts) + .PrintToString(firstPerson); + + result.Should().Be(expected); + } + + [Test] + public void Exclude_ExcludeAllTypes() + { + const string expected = "Person\r\n"; + var result = ObjectPrinter.For() + .Exclude() + .Exclude() + .Exclude() + .Exclude() + .Exclude() + .Exclude() + .Exclude>() + .PrintToString(firstPerson); + + result.Should().Be(expected); + } + + [Test] + public void Using_SerializationForString() + { + const string expected = " is beautiful"; + var result = ObjectPrinter.For() + .PrintSettings() + .Using(p => $"{p} is beautiful") + .PrintToString(firstPerson); + + result.Should().Contain(expected); + } + + [Test] + public void Using_SerializationForInt() + { + const string exceptedAge = $"{nameof(firstPerson.Age)} = 10"; + var result = ObjectPrinter.For() + .PrintSettings() + .Using(_ => "10") + .PrintToString(firstPerson); + + result.Should().Contain(exceptedAge); + } + + [Test] + public void Using_SerializationForName() + { + var expected = firstPerson.Name?.ToUpper(); + var unexpected = firstPerson.Name; + var result = ObjectPrinter.For() + .PrintPropertySettings(p => p.Name) + .Using(p => p!.ToUpper()) + .PrintToString(firstPerson); + + result.Should().Contain(expected).And.NotContain(unexpected); + } + + [Test] + public void Using_MultiplePropertySerializations() + { + var exceptedAge = $"{nameof(firstPerson.Name)} = 21{firstPerson.Name}12"; + var result = ObjectPrinter.For() + .PrintPropertySettings(p => p.Name) + .Using(name => $"1{name}1") + .PrintPropertySettings(p => p.Name) + .Using(name => $"2{name}2") + .PrintToString(firstPerson); + + result.Should().Contain(exceptedAge); + } + + [Test] + public void Using_MultipleTypeSerializationsForString() + { + var exceptedName = $"{nameof(firstPerson.Name)} = 21{firstPerson.Name}12"; + var result = ObjectPrinter.For() + .PrintSettings() + .Using(str => $"1{str}1") + .PrintSettings() + .Using(str => $"2{str}2") + .PrintToString(firstPerson); + + result.Should().Contain(exceptedName); + } + + [Test] + public void Using_TrimmedToPropertiesAfterSerializationForName([Values(0, 1, 2)] int length) + { + var exceptedName = $"{nameof(firstPerson.Name)} = " + $"1{firstPerson.Name!}"[..length]; + var result = ObjectPrinter.For() + .PrintPropertySettings(p => p.Name) + .Using(name => $"1{name}1") + .PrintPropertySettings(p => p.Name) + .TrimmedTo(length) + .PrintToString(firstPerson); + + result.Should().Contain(exceptedName); + } + + [Test] + public void UseCulture_ChangeCultureForDouble() + { + var expected = firstPerson.Height.ToString(new CultureInfo("ru-RU")); + var unexpected = firstPerson.Height.ToString(new CultureInfo("en-US")); + var result = ObjectPrinter.For() + .UseCulture(new("ru-RU")) + .PrintToString(firstPerson); + + result.Should().Contain(expected).And.NotContain(unexpected); + } + + [Test] + public void TrimmedTo_TrimmingName([Values(0, 1, 2)] int length) + { + var expected = $"{nameof(firstPerson.Name)} = {firstPerson.Name?[..length]}"; + var unexpected = firstPerson.Name; + var result = ObjectPrinter.For() + .PrintPropertySettings(p => p.Name) + .TrimmedTo(length) + .PrintToString(firstPerson); + + result.Should().Contain(expected).And.NotContain(unexpected); + } + + [TestCase(0)] + [TestCase(1)] + [TestCase(2)] + public void SetMaxNestingLevel_WithDifferentNestingLevels(int levelNesting) + { + firstPerson.BestFriend = secondPerson; + var expected = $"The maximum recursion depth has been reached: {levelNesting}."; + var result = ObjectPrinter.For() + .SetMaxNestingLevel(levelNesting) + .PrintToString(firstPerson); + + result.Should().Contain(expected); + } + + [TestCase(-1)] + [TestCase(-2)] + [TestCase(-3)] + [TestCase(-4)] + [TestCase(-5)] + public void SetMaxNestingLevel_WithNegativeNestingLevel(int levelNesting) + { + FluentActions.Invoking(() => ObjectPrinter.For() + .SetMaxNestingLevel(levelNesting)) + .Should().Throw(); + } + + [Test] + public void PrintToString_ProcessingCyclicLinksBetweenObjects() + { + firstPerson.BestFriend = secondPerson; + secondPerson.BestFriend = firstPerson; + + var result = ObjectPrinter.For() + .PrintToString(firstPerson); + result.Should().Contain("It is not possible to print an object with a circular reference."); + } + + [Test] + public void PrintToString_WhenLinkIsSame_ButThereAreNoCircularReferences_Array() + { + var result = ObjectPrinter.For() + .PrintToString([firstPerson, firstPerson]); + + result.Should().NotContain("It is not possible to print an object with a circular reference."); + } + + [Test] + public void PrintToString_WhenLinkIsSame_ButThereAreNoCircularReferences_List() + { + var result = ObjectPrinter.For>() + .PrintToString([firstPerson, firstPerson]); + + result.Should().NotContain("It is not possible to print an object with a circular reference."); + } + + [Test] + public void PrintToString_WhenLinkIsSame_ButThereAreNoCircularReferences_Dictionary() + { + var result = ObjectPrinter.For>() + .PrintToString(new() { { firstPerson, firstPerson } }); + + result.Should().NotContain("It is not possible to print an object with a circular reference."); + } + + [Test] + public void PrintToString_WhenLinkIsSame_ButThereAreNoCircularReferences_InsideObject() + { + family = family with { Mom = firstPerson, Dad = firstPerson }; + var result = ObjectPrinter.For() + .PrintToString(family); + + result.Should().NotContain("It is not possible to print an object with a circular reference."); + } + + [Test] + public void PrintToString_CorrectStructure() + { + var expectedResult = new StringBuilder(); + expectedResult.Append("Person\r\n\t" + + "Id = 00000000-0000-0000-0000-000000000000\r\n\t" + + "Name = \"Ben\"\r\n\t" + + "Surname = \"Big\"\r\n\t" + + "Height = 170,1\r\n\t" + + "Age = 20\r\n\t" + + "BestFriend = Person\r\n\t\t" + + "Id = 00000000-0000-0000-0000-000000000000\r\n\t\t" + + "Name = \"Bob\"\r\n\t\t" + + "Surname = \"Boby\"\r\n\t\t" + + "Height = 40\r\n\t\t" + + "Age = 80\r\n\t\t" + + "BestFriend = null\r\n\t\t" + + "Friends = {}\r\n\t\t" + + "BodyParts = {}\r\n\r\n\t" + + "Friends = {\r\n\t\t" + + "Person\r\n\t\t\t" + + "Id = 00000000-0000-0000-0000-000000000000\r\n\t\t\t" + + "Name = \"Alice\"\r\n\t\t\t" + + "Surname = \"Sev\"\r\n\t\t\t" + + "Height = 50\r\n\t\t\t" + + "Age = 30\r\n\t\t\t" + + "BestFriend = null\r\n\t\t\t" + + "Friends = {}\r\n\t\t\t" + + "BodyParts = {}\r\n\t\t,\r\n\t\t" + + "Person\r\n\t\t\t" + + "Id = 00000000-0000-0000-0000-000000000000\r\n\t\t\t" + + "Name = \"Max\"\r\n\t\t\t" + + "Surname = \"Albor\"\r\n\t\t\t" + + "Height = 10\r\n\t\t\t" + + "Age = 9\r\n\t\t\t" + + "BestFriend = null\r\n\t\t\t" + + "Friends = {}\r\n\t\t\t" + + "BodyParts = {}\r\n\t\t" + + "}\r\n\r\n\t" + + "BodyParts = {\r\n\t\t\"Hand\": 2\r\n\t\t\"Foot\": 2\r\n\t\t\"Head\": 1\r\n\t\t\"Tail\": 0\r\n\t\t}\r\n\r\n"); + + var result = expectedResult.ToString(); + var v2 = ObjectPrinter.For() + .PrintToString(firstPerson); + + result.Should().Contain(v2); + } + + [Test] + public void PrintToString_WithExcludedProperiesStructure() + { + var expectedResult = new StringBuilder(); + expectedResult.Append("Person\r\n\t" + + "Id = 00000000-0000-0000-0000-000000000000\r\n\t" + + "Surname = \"Big\"\r\n\t" + + "Height = 170,1\r\n\t" + + "BestFriend = Person\r\n\t\t" + + "Id = 00000000-0000-0000-0000-000000000000\r\n\t\t" + + "Surname = \"Boby\"\r\n\t\t" + + "Height = 40\r\n\t\t" + + "BestFriend = null\r\n\t\t" + + "Friends = {}\r\n\t\t" + + "BodyParts = {}\r\n\r\n\t" + + "Friends = {\r\n\t\t" + + "Person\r\n\t\t\t" + + "Id = 00000000-0000-0000-0000-000000000000\r\n\t\t\t" + + "Surname = \"Sev\"\r\n\t\t\t" + + "Height = 50\r\n\t\t\t" + + "BestFriend = null\r\n\t\t\t" + + "Friends = {}\r\n\t\t\t" + + "BodyParts = {}\r\n\t\t,\r\n\t\t" + + "Person\r\n\t\t\t" + + "Id = 00000000-0000-0000-0000-000000000000\r\n\t\t\t" + + "Surname = \"Albor\"\r\n\t\t\t" + + "Height = 10\r\n\t\t\t" + + "BestFriend = null\r\n\t\t\t" + + "Friends = {}\r\n\t\t\t" + + "BodyParts = {}\r\n\t\t" + + "}\r\n\r\n\t" + + "BodyParts = {\r\n\t\t\"Hand\": 2\r\n\t\t\"Foot\": 2\r\n\t\t\"Head\": 1\r\n\t\t\"Tail\": 0\r\n\t\t}\r\n\r\n"); + + var result = expectedResult.ToString(); + var v2 = ObjectPrinter.For() + .Exclude(p => p.Name) + .Exclude(p => p.Age) + .PrintToString(firstPerson); + + result.Should().Contain(v2); + } +} \ No newline at end of file diff --git a/ObjectPrinting/IPropertyPrintingConfig.cs b/ObjectPrinting/IPropertyPrintingConfig.cs new file mode 100644 index 00000000..08037fed --- /dev/null +++ b/ObjectPrinting/IPropertyPrintingConfig.cs @@ -0,0 +1,8 @@ +using System; + +namespace ObjectPrinting; + +public interface IPropertyPrintingConfig +{ + public PrintingConfig Using(Func func); +} \ No newline at end of file diff --git a/ObjectPrinting/ITypePrintingConfig.cs b/ObjectPrinting/ITypePrintingConfig.cs new file mode 100644 index 00000000..b10f0788 --- /dev/null +++ b/ObjectPrinting/ITypePrintingConfig.cs @@ -0,0 +1,8 @@ +using System; + +namespace ObjectPrinting; + +public interface ITypePrintingConfig +{ + public PrintingConfig Using(Func func); +} \ No newline at end of file diff --git a/ObjectPrinting/ObjectPrinter.cs b/ObjectPrinting/ObjectPrinter.cs index 3c7867c3..b2ce90a7 100644 --- a/ObjectPrinting/ObjectPrinter.cs +++ b/ObjectPrinting/ObjectPrinter.cs @@ -1,10 +1,9 @@ -namespace ObjectPrinting +namespace ObjectPrinting; + +public class ObjectPrinter { - public class ObjectPrinter + public static PrintingConfig For() { - public static PrintingConfig For() - { - return new PrintingConfig(); - } + return new PrintingConfig(); } } \ No newline at end of file diff --git a/ObjectPrinting/ObjectPrinterExtensions.cs b/ObjectPrinting/ObjectPrinterExtensions.cs new file mode 100644 index 00000000..88c4d583 --- /dev/null +++ b/ObjectPrinting/ObjectPrinterExtensions.cs @@ -0,0 +1,11 @@ +using System; + +namespace ObjectPrinting; + +public static class ObjectPrinterExtensions +{ + public static string PrintToString(this T obj) => ObjectPrinter.For().PrintToString(obj); + + public static string PrintToString(this T obj, Func, PrintingConfig> config) => + config(ObjectPrinter.For()).PrintToString(obj); +} \ No newline at end of file diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs index a9e08211..04adfd05 100644 --- a/ObjectPrinting/PrintingConfig.cs +++ b/ObjectPrinting/PrintingConfig.cs @@ -1,41 +1,225 @@ using System; -using System.Linq; +using System.Collections; +using System.Collections.Generic; using System.Text; +using System.Globalization; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; + +namespace ObjectPrinting; -namespace ObjectPrinting +public class PrintingConfig { - public class PrintingConfig + private readonly HashSet typesToExclude = new(ReferenceEqualityComparer.Instance); + private readonly HashSet propertiesToExclude = []; + private readonly HashSet processedObjects = new(ReferenceEqualityComparer.Instance); + private readonly Dictionary cultureSerializers = []; + private readonly Dictionary> typeSerializers = []; + private readonly Dictionary> propertySerializers = []; + private int MaxNestingLevel { get; set; } = 5; + + public PrintingConfig SetMaxNestingLevel(int maxNestingLevel) + { + if (maxNestingLevel < 0) + { + throw new ArgumentException("Max nesting level must be greater than or equal to 0."); + } + + MaxNestingLevel = maxNestingLevel; + return this; + } + + public PrintingConfig Exclude() + { + typesToExclude.Add(typeof(TPropType)); + return this; + } + + public PrintingConfig Exclude(Expression> propertySelector) + { + propertiesToExclude.Add(GetPropertyMemberInfo(propertySelector).Name); + return this; + } + + public IPropertyPrintingConfig PrintPropertySettings( + Expression> propertySelector) + { + return new PropertyPrintingConfig(this, GetPropertyMemberInfo(propertySelector).Name); + } + + public ITypePrintingConfig PrintSettings() + { + return new TypePrintingConfig(this); + } + + public PrintingConfig UseCulture(CultureInfo culture) where TPropType : IFormattable + { + cultureSerializers[typeof(TPropType)] = culture; + return this; + } + + internal void AddTypeSerializer(Func serializeFunc) + { + if (typeSerializers.TryGetValue(typeof(TPropType), out var serializers)) + { + serializers.Add(serializeFunc); + return; + } + typeSerializers[typeof(TPropType)] = [serializeFunc]; + } + + internal void AddPropertySerializer(string propertyName, Func serializeFunc) + { + if (propertySerializers.TryGetValue(propertyName, out var serializers)) + { + serializers.Add(serializeFunc); + return; + } + propertySerializers[propertyName] = [serializeFunc]; + } + + public string PrintToString(TOwner obj) + { + return PrintToString(obj, 1); + } + + private string PrintToString(object? obj, int nestingLevel) + { + if (obj is null) + { + return "null"; + } + + if (processedObjects.Contains(obj)) + { + return "It is not possible to print an object with a circular reference."; + } + + var type = obj.GetType(); + + if (typeSerializers.TryGetValue(type, out var serializers)) + { + var result = serializers.Aggregate(obj, (current, serializer) => serializer.DynamicInvoke(current)!); + + return result.ToString()!; + } + + if (type.IsPrimitive || type == typeof(string) || type == typeof(Guid)) + { + return type == typeof(string) ? $"\"{obj}\"" : obj.ToString()!; + } + + if (nestingLevel > MaxNestingLevel) + { + return $"The maximum recursion depth has been reached: {MaxNestingLevel}."; + } + + processedObjects.Add(obj); + if (obj is IEnumerable enumerable && type != typeof(string)) + { + processedObjects.Clear(); + return SerializeEnumerable(enumerable, nestingLevel); + } + + var indentation = new string('\t', nestingLevel); + var stringBuilderResult = new StringBuilder(); + stringBuilderResult.AppendLine($"{type.Name}"); + + foreach (var propertyInfo in type.GetProperties()) + { + if (propertiesToExclude.Contains(propertyInfo.Name) || typesToExclude.Contains(propertyInfo.PropertyType)) + { + continue; + } + + var propertyValue = propertyInfo.GetValue(obj); + var propertyType = propertyInfo.PropertyType; + var propertyName = propertyInfo.Name; + + stringBuilderResult.AppendLine(GetSerializeString(nestingLevel, propertyName, indentation, propertyValue, + propertyType)); + } + + return stringBuilderResult.ToString(); + } + + private string GetSerializeString( + int nestingLevel, + string propertyName, + string indentation, + object? propertyValue, + Type propertyType + ) { - public string PrintToString(TOwner obj) + if (propertySerializers.TryGetValue(propertyName, out var serializers)) { - return PrintToString(obj, 0); + var result = + serializers.Aggregate(propertyValue, (current, serializer) => serializer.DynamicInvoke(current)); + + return $"{indentation}{propertyName} = {result!}"; } - private string PrintToString(object obj, int nestingLevel) + if (cultureSerializers.TryGetValue(propertyType, out var culture)) { - //TODO apply configurations - if (obj == null) - return "null" + Environment.NewLine; + var propertyValueFormattable = propertyValue as IFormattable; + + return $"{indentation}{propertyName} = {propertyValueFormattable!.ToString(null, culture)}"; + } + + return $"{indentation}{propertyName} = {PrintToString(propertyValue, nestingLevel + 1)}"; + } + + private string SerializeEnumerable(IEnumerable enumerable, int nestingLevel) + { + var objects = enumerable.Cast().ToList(); + + if (objects.Count == 0) + { + return "{}"; + } + + var indentation = new string('\t', nestingLevel); + var serializeResult = new StringBuilder(); + serializeResult.AppendLine("{"); - var finalTypes = new[] + if (enumerable is IDictionary dictionary) + { + foreach (var key in dictionary.Keys) { - 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()) + var value = dictionary[key]; + var keyConvert = PrintToString(key, nestingLevel); + var valueConvert = PrintToString(value, nestingLevel + 1); + + serializeResult.Append($"{indentation}{keyConvert}: {valueConvert}" + Environment.NewLine); + } + } + else + { + for (var i = 0; i < objects.Count; i++) { - sb.Append(identation + propertyInfo.Name + " = " + - PrintToString(propertyInfo.GetValue(obj), - nestingLevel + 1)); + var obj = objects[i]; + serializeResult.Append($"{indentation}{PrintToString(obj, nestingLevel + 1)}"); + if (i < objects.Count - 1) + { + serializeResult.Append($"{indentation},"); + serializeResult.Append(Environment.NewLine); + } } - return sb.ToString(); } + + serializeResult.AppendLine($"{indentation}}}"); + + return serializeResult.ToString(); + } + + private static MemberInfo GetPropertyMemberInfo(Expression> propertySelector) + { + return propertySelector.Body switch + { + MemberExpression memberExpression => memberExpression.Member, + UnaryExpression { Operand: MemberExpression operand } => operand.Member, + var _ => throw new ArgumentException("Invalid property selector expression") + }; } } \ No newline at end of file diff --git a/ObjectPrinting/PropertyPrintingConfig.cs b/ObjectPrinting/PropertyPrintingConfig.cs new file mode 100644 index 00000000..f26b0553 --- /dev/null +++ b/ObjectPrinting/PropertyPrintingConfig.cs @@ -0,0 +1,17 @@ +using System; + +namespace ObjectPrinting; + +public class PropertyPrintingConfig(PrintingConfig config, string propertyNameName) + : IPropertyPrintingConfig +{ + private PrintingConfig Config { get; } = config; + private string PropertyName { get; } = propertyNameName; + + public PrintingConfig Using(Func func) + { + Config.AddPropertySerializer(PropertyName, func); + + return Config; + } +} \ No newline at end of file diff --git a/ObjectPrinting/PropertyPrintingConfigExtensions.cs b/ObjectPrinting/PropertyPrintingConfigExtensions.cs new file mode 100644 index 00000000..0b5e8ef8 --- /dev/null +++ b/ObjectPrinting/PropertyPrintingConfigExtensions.cs @@ -0,0 +1,11 @@ +namespace ObjectPrinting; + +public static class PropertyPrintingConfigExtensions +{ + public static PrintingConfig TrimmedTo( + this IPropertyPrintingConfig config, + int length) + { + return config.Using(str => str == null ? "null" : str[..length]); + } +} \ No newline at end of file diff --git a/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs b/ObjectPrinting/Tests/ObjectPrinterAcceptanceTests.cs deleted file mode 100644 index 4c8b2445..00000000 --- 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/Person.cs b/ObjectPrinting/Tests/Person.cs deleted file mode 100644 index f9555955..00000000 --- a/ObjectPrinting/Tests/Person.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -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; } - } -} \ No newline at end of file diff --git a/ObjectPrinting/TypePrintingConfig.cs b/ObjectPrinting/TypePrintingConfig.cs new file mode 100644 index 00000000..e9c141dc --- /dev/null +++ b/ObjectPrinting/TypePrintingConfig.cs @@ -0,0 +1,16 @@ +using System; + +namespace ObjectPrinting; + +public class TypePrintingConfig(PrintingConfig config) + : ITypePrintingConfig +{ + private PrintingConfig Config { get; } = config; + + public PrintingConfig Using(Func func) + { + Config.AddTypeSerializer(func); + + return Config; + } +} \ No newline at end of file diff --git a/fluent-api.sln b/fluent-api.sln index 69c8db9e..123b3491 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}") = "ObjectPrinting.Test", "ObjectPrinting.Test\ObjectPrinting.Test.csproj", "{B7E0BF43-A392-4EA2-9E19-90B9C2FEC5E1}" +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 + {B7E0BF43-A392-4EA2-9E19-90B9C2FEC5E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B7E0BF43-A392-4EA2-9E19-90B9C2FEC5E1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B7E0BF43-A392-4EA2-9E19-90B9C2FEC5E1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B7E0BF43-A392-4EA2-9E19-90B9C2FEC5E1}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/fluent-api.sln.DotSettings b/fluent-api.sln.DotSettings index 135b83ec..229f449d 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" 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" Prefix="" Suffix="" Style="AaBb_AaBb" /></Policy> + True True True Imported 10.10.2016