diff --git a/ObjectPrinting/Contracts/ISerializer.cs b/ObjectPrinting/Contracts/ISerializer.cs new file mode 100644 index 00000000..4cbb4578 --- /dev/null +++ b/ObjectPrinting/Contracts/ISerializer.cs @@ -0,0 +1,6 @@ +namespace ObjectPrinting.Contracts; + +public interface ISerializer +{ + public T Serialize(object instance, int nestingLevel); +} \ No newline at end of file diff --git a/ObjectPrinting/ObjectPrinter.cs b/ObjectPrinting/ObjectPrinter.cs deleted file mode 100644 index 3c7867c3..00000000 --- a/ObjectPrinting/ObjectPrinter.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace ObjectPrinting -{ - public class ObjectPrinter - { - public static PrintingConfig For() - { - return new PrintingConfig(); - } - } -} \ No newline at end of file diff --git a/ObjectPrinting/ObjectPrinting.csproj b/ObjectPrinting/ObjectPrinting.csproj index 1c5eaf1c..ec7a36a4 100644 --- a/ObjectPrinting/ObjectPrinting.csproj +++ b/ObjectPrinting/ObjectPrinting.csproj @@ -1,14 +1,9 @@  - - 8 - netcoreapp3.1 - false - + + net6.0 + enable + false + - - - - - - + \ No newline at end of file diff --git a/ObjectPrinting/PrintingConfig.cs b/ObjectPrinting/PrintingConfig.cs deleted file mode 100644 index a9e08211..00000000 --- a/ObjectPrinting/PrintingConfig.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Linq; -using System.Text; - -namespace ObjectPrinting -{ - public class PrintingConfig - { - 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/SerializingOptions.cs b/ObjectPrinting/SerializingOptions.cs new file mode 100644 index 00000000..04b9c1d7 --- /dev/null +++ b/ObjectPrinting/SerializingOptions.cs @@ -0,0 +1,8 @@ +namespace ObjectPrinting; + +public struct SerializingOptions +{ + public int NestingLevel { get; init; } + public string ElementOffset { get; init; } + public string PreviousIndent { get; init; } +} \ No newline at end of file diff --git a/ObjectPrinting/Solved/ObjectExtensions.cs b/ObjectPrinting/Solved/ObjectExtensions.cs deleted file mode 100644 index b0c94553..00000000 --- a/ObjectPrinting/Solved/ObjectExtensions.cs +++ /dev/null @@ -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 540ee769..00000000 --- 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 0ec5aeb2..00000000 --- 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 deleted file mode 100644 index a509697d..00000000 --- a/ObjectPrinting/Solved/PropertyPrintingConfig.cs +++ /dev/null @@ -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 deleted file mode 100644 index dd392239..00000000 --- a/ObjectPrinting/Solved/PropertyPrintingConfigExtensions.cs +++ /dev/null @@ -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 ac52d5ee..00000000 --- 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 858ebbf8..00000000 --- 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/StringSerializer.cs b/ObjectPrinting/StringSerializer.cs new file mode 100644 index 00000000..ac58d38b --- /dev/null +++ b/ObjectPrinting/StringSerializer.cs @@ -0,0 +1,197 @@ +using ObjectPrinting.Contracts; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; + +namespace ObjectPrinting; + +public sealed class StringSerializer : ISerializer +{ + private readonly Type[] finalTypes = + { + typeof(int), typeof(double), typeof(float), typeof(string), + typeof(DateTime), typeof(TimeSpan) + }; + + private readonly HashSet ignoredProperties = new(); + private readonly HashSet ignoredTypes = new(); + private readonly Dictionary propertySerializers = new(); + private readonly Dictionary typeSerializers = new(); + private readonly HashSet visitedObjects = new(); + + private int? lineLength; + + public string Serialize(object instance, int nestingLevel = 0) + { + var instanceType = instance.GetType(); + var properties = instanceType.GetProperties(); + + var stringBuilder = new StringBuilder(); + var indentation = new string('\t', nestingLevel + 1); + + if (finalTypes.Contains(instanceType)) + { + if (instance is IConvertible convertible) + return convertible.ToString(CultureInfo.CurrentCulture); + + return instance.ToString()!; + } + + if (instance is IEnumerable enumerable) + return SerializeEnumerable(enumerable, nestingLevel); + + stringBuilder.AppendLine(instanceType.Name); + + var filteredProperties = ExcludeIgnoredProperties(properties); + + visitedObjects.Add(instance); + + SerializeField(instance, nestingLevel, filteredProperties, stringBuilder, indentation); + + return stringBuilder.ToString(); + } + + public StringSerializer Ignoring() + { + ignoredTypes.Add(typeof(TPropertyType)); + return this; + } + + public StringSerializer Ignoring(Expression> selector) + { + ignoredProperties.Add(((MemberExpression)selector.Body).Member); + return this; + } + + public StringSerializer ChangeSerializingFor( + Expression> selector, + Func serializer) + { + propertySerializers.TryAdd(((MemberExpression)selector.Body).Member, serializer); + return this; + } + + public StringSerializer ChangeSerializingFor(Func serializer) + { + typeSerializers.TryAdd(typeof(TPropertyType), serializer); + return this; + } + + public StringSerializer TrimLinesTo(int length) + { + lineLength = length; + return this; + } + + private void SerializeField( + object instance, + int nestingLevel, + IEnumerable filteredProperties, + StringBuilder stringBuilder, string indentation) + { + foreach (var propertyInfo in filteredProperties) + { + var propertyValue = propertyInfo.GetValue(instance); + var propertyName = propertyInfo.Name; + + if (propertyValue == null) + { + AddPropertyToBuilder(stringBuilder, indentation, propertyName, "Null"); + continue; + } + + // Check for circular reference: + if (visitedObjects.Contains(propertyValue)) + continue; + + var serializer = FindSerializer(propertyInfo); + + var output = serializer != null + ? serializer.Method.Invoke(serializer.Target, new[] { propertyValue })!.ToString()! + : Serialize(propertyValue, nestingLevel + 1); + + AddPropertyToBuilder(stringBuilder, indentation, propertyName, output); + } + } + + private void AddPropertyToBuilder( + StringBuilder stringBuilder, + string indentation, + string propertyName, + string output) + { + var stringValue = $"{propertyName} = {output}"; + stringBuilder.Append(indentation + GetTrimmedString(stringValue) + Environment.NewLine); + } + + private IEnumerable ExcludeIgnoredProperties(IEnumerable properties) + { + var filteredProperties = properties + .Where(property => !ignoredProperties.Contains(property)) + .Where(property => !ignoredTypes.Contains(property.PropertyType)); + + return filteredProperties; + } + + private string SerializeEnumerable(IEnumerable enumerable, int nestingLevel) + { + const int itemOffset = 4; + + var stringBuilder = new StringBuilder(); + var options = new SerializingOptions + { + ElementOffset = new string(' ', itemOffset), + PreviousIndent = new string('\t', nestingLevel), + NestingLevel = nestingLevel + }; + + stringBuilder.AppendLine("["); + + if (enumerable is IDictionary dict) + AddDictionaryBody(dict, stringBuilder, options); + else + AddSequenceBody(enumerable, stringBuilder, options); + + stringBuilder.Append($"{options.PreviousIndent}]"); + + return stringBuilder.ToString(); + } + + private void AddDictionaryBody(IDictionary dict, StringBuilder stringBuilder, SerializingOptions options) + { + foreach (var key in dict.Keys) + { + var output = Serialize(dict[key]!, options.NestingLevel + 1); + stringBuilder.AppendLine($"{options.PreviousIndent}{options.ElementOffset}[{key}] => {output.TrimEnd()},"); + } + } + + private void AddSequenceBody(IEnumerable enumerable, StringBuilder stringBuilder, SerializingOptions options) + { + foreach (var element in enumerable) + { + var output = Serialize(element, options.NestingLevel + 1); + stringBuilder.AppendLine($"{options.PreviousIndent}{options.ElementOffset}{output.TrimEnd()},"); + } + } + + private Delegate? FindSerializer(PropertyInfo propertyInfo) + { + var memberSerializer = propertySerializers.GetValueOrDefault(propertyInfo); + var typeSerializer = typeSerializers.GetValueOrDefault(propertyInfo.PropertyType); + return memberSerializer ?? typeSerializer; + } + + private string GetTrimmedString(string value) + { + if (lineLength == null) + return value; + + return value.Length <= lineLength ? value : value[..lineLength.Value]; + } +} \ 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/Samples/FluentMapper.Tests/BasicTests.cs b/Samples/FluentMapper.Tests/BasicTests.cs index 6435ecf1..f511fc4c 100644 --- a/Samples/FluentMapper.Tests/BasicTests.cs +++ b/Samples/FluentMapper.Tests/BasicTests.cs @@ -9,9 +9,9 @@ public sealed class BasicTests public void BasicMapping() { var mapper = FluentMapper - .ThatMaps() - .From() - .Create(); + .ThatMaps() + .From() + .Create(); Assert.That(mapper, Is.Not.Null); @@ -35,4 +35,4 @@ public sealed class Source public int Num { get; set; } } } -} +} \ No newline at end of file diff --git a/Samples/FluentMapper.Tests/FluentMapping.Tests.csproj b/Samples/FluentMapper.Tests/FluentMapping.Tests.csproj index 0d5262d4..6df35244 100644 --- a/Samples/FluentMapper.Tests/FluentMapping.Tests.csproj +++ b/Samples/FluentMapper.Tests/FluentMapping.Tests.csproj @@ -1,18 +1,18 @@  - - 8 - netcoreapp3.1 - false - + + 8 + netcoreapp3.1 + false + - - - - + + + + - - - + + + diff --git a/Samples/FluentMapper.Tests/NullSourceTests.cs b/Samples/FluentMapper.Tests/NullSourceTests.cs index 155bb2a7..31d04109 100644 --- a/Samples/FluentMapper.Tests/NullSourceTests.cs +++ b/Samples/FluentMapper.Tests/NullSourceTests.cs @@ -10,9 +10,9 @@ public sealed class NullSourceTests public void DefaultBehavior() { var mapper = FluentMapper - .ThatMaps() - .From() - .Create(); + .ThatMaps() + .From() + .Create(); var ex = Assert.Throws(() => { @@ -20,18 +20,18 @@ public void DefaultBehavior() }); Assert.That(ex.Message, Does.StartWith("Cannot map instance of Target from " + - "null instance of Source.")); + "null instance of Source.")); } [Test] public void ReturnNull() { var mapper = FluentMapper - .ThatMaps() - .From() - .WithNullSource() - .ReturnNull() - .Create(); + .ThatMaps() + .From() + .WithNullSource() + .ReturnNull() + .Create(); var target = mapper.Map(null); @@ -46,11 +46,11 @@ public void ReturnNull() public void CreateEmptyObject() { var mapper = FluentMapper - .ThatMaps() - .From() - .WithNullSource() - .CreateEmpty() - .Create(); + .ThatMaps() + .From() + .WithNullSource() + .CreateEmpty() + .Create(); var target = mapper.Map(null); @@ -74,4 +74,4 @@ public sealed class Source public int Num { get; set; } } } -} +} \ No newline at end of file diff --git a/Samples/FluentMapper.Tests/SimplePropertyMappingTests.cs b/Samples/FluentMapper.Tests/SimplePropertyMappingTests.cs index 513877a4..b5eb97c3 100644 --- a/Samples/FluentMapper.Tests/SimplePropertyMappingTests.cs +++ b/Samples/FluentMapper.Tests/SimplePropertyMappingTests.cs @@ -16,8 +16,8 @@ public void InvalidMapping() var ex = Assert.Throws(() => spec.Create()); var expectedMessage = "Unmapped properties: " + - "Target.Num, Target.Str, " + - "Source.ID, Source.Name"; + "Target.Num, Target.Str, " + + "Source.ID, Source.Name"; Assert.That(ex.Message, Is.EqualTo(expectedMessage)); } @@ -31,7 +31,8 @@ public void PropertyMatching() .ThatSets(tgt => tgt.Num).From(src => src.ID) .Create(); - var source = new Source { + var source = new Source + { ID = 7, Name = "Bob" }; @@ -71,8 +72,8 @@ public void IgnoringSourceProperty_NonPropertyExpression() var ex = Assert.Throws(() => spec.IgnoringSourceProperty(src => 7)); Assert.That(ex.Message, Is.EqualTo("IgnoringSourceProperty(...) requires an expression " - + "that is a simple property access of the form 'src => src.Property'." - )); + + "that is a simple property access of the form 'src => src.Property'." + )); } [Test] @@ -83,16 +84,15 @@ public void IgnoringTargetProperty_MethodCallExpression() var ex = Assert.Throws(() => spec.IgnoringTargetProperty(tgt => tgt.AField)); Assert.That(ex.Message, Is.EqualTo("IgnoringTargetProperty(...) requires an expression " - + "that is a simple property access of the form 'tgt => tgt.Property'." - )); + + "that is a simple property access of the form 'tgt => tgt.Property'." + )); } public sealed class Target { + public int AField; public string Str { get; set; } public int Num { get; set; } - - public int AField; } public sealed class Source @@ -101,4 +101,4 @@ public sealed class Source public int ID { get; set; } } } -} +} \ No newline at end of file diff --git a/Samples/FluentMapper.Tests/Unit/SetterSpecTests.cs b/Samples/FluentMapper.Tests/Unit/SetterSpecTests.cs index 70935db6..36745d64 100644 --- a/Samples/FluentMapper.Tests/Unit/SetterSpecTests.cs +++ b/Samples/FluentMapper.Tests/Unit/SetterSpecTests.cs @@ -12,7 +12,7 @@ public void Ctor() var spec = new TypeMappingSpec(); var testee = spec.ThatSets(tgt => tgt.TargetProp) - as ISetterSpecProperties + as ISetterSpecProperties ; Assert.That(testee.Spec, Is.SameAs(spec)); @@ -25,8 +25,8 @@ as ISetterSpecProperties public void From_Creates_Mapping() { var result = new TypeMappingSpec() - .ThatSets(tgt => tgt.TargetProp).From(src => src.SourceProp) - as ITypeMappingSpecProperties + .ThatSets(tgt => tgt.TargetProp).From(src => src.SourceProp) + as ITypeMappingSpecProperties ; Assert.That( @@ -48,9 +48,9 @@ as ITypeMappingSpecProperties public void MultipleMappings() { var result = new TypeMappingSpec() - .ThatSets(tgt => tgt.TargetProp).From(src => src.SourceProp) - .ThatSets(tgt => tgt.MatchingProp).From(src => src.MatchingProp) - as ITypeMappingSpecProperties + .ThatSets(tgt => tgt.TargetProp).From(src => src.SourceProp) + .ThatSets(tgt => tgt.MatchingProp).From(src => src.MatchingProp) + as ITypeMappingSpecProperties ; Assert.That(result.SourceProperties, Is.Empty); @@ -80,4 +80,4 @@ public sealed class Target public string TargetProp { get; set; } } } -} +} \ No newline at end of file diff --git a/Samples/FluentMapper.Tests/Unit/TypeMappingSpecTests.cs b/Samples/FluentMapper.Tests/Unit/TypeMappingSpecTests.cs index 85bcf5ba..d8d83e80 100644 --- a/Samples/FluentMapper.Tests/Unit/TypeMappingSpecTests.cs +++ b/Samples/FluentMapper.Tests/Unit/TypeMappingSpecTests.cs @@ -13,9 +13,9 @@ public sealed class TypeMappingSpecTests public void GathersPropertiesInCtor() { var testee = new TypeMappingSpec() - as ITypeMappingSpecProperties + as ITypeMappingSpecProperties ; - + Assert.That( testee.SourceProperties.Count(), Is.EqualTo(2), @@ -23,11 +23,11 @@ as ITypeMappingSpecProperties Assert.That( testee.SourceProperties.All(x => x.DeclaringType == typeof(Source)), "Expected SourceProperties to all have DeclaringType of Source." - ); + ); Assert.That( testee.SourceProperties.Select(x => x.Name), Is.EquivalentTo(new[] { "Prop1", "Prop2" }) - ); + ); Assert.That( testee.TargetProperties.Count(), Is.EqualTo(2), @@ -35,11 +35,11 @@ as ITypeMappingSpecProperties Assert.That( testee.TargetProperties.All(x => x.DeclaringType == typeof(Target)), "Expected TargetProperties to all have DeclaringType of Target." - ); + ); Assert.That( testee.TargetProperties.Select(x => x.Name), Is.EquivalentTo(new[] { "Prop3", "Prop4" }) - ); + ); } [Test] @@ -54,14 +54,14 @@ public void PropertiesVisibility() [Test] public void PropertiesSubset_ViaCtor() { - var testee = - new TypeMappingSpec().Transforms() - .WithTargetProperties( - typeof(Target).GetProperties().Where(x => x.Name != "Prop3") + var testee = + new TypeMappingSpec().Transforms() + .WithTargetProperties( + typeof(Target).GetProperties().Where(x => x.Name != "Prop3") ).Transforms() - .WithSourceProperties( - typeof(Source).GetProperties().Where(x => x.Name != "Prop1") - ).Properties(); + .WithSourceProperties( + typeof(Source).GetProperties().Where(x => x.Name != "Prop1") + ).Properties(); Assert.That(testee.SourceProperties.Count(), Is.EqualTo(1)); Assert.That(testee.TargetProperties.Count(), Is.EqualTo(1)); @@ -76,12 +76,15 @@ public void PropertiesSubset_ViaCtor() [Test] public void MappingAction_Ctor() { - var action = new Action((tgt, src) => { /* no-op */ }); + var action = new Action((tgt, src) => + { + /* no-op */ + }); var testee = new TypeMappingSpec() .Transforms().WithSourceProperties(new PropertyInfo[0]) .Transforms().WithTargetProperties(new PropertyInfo[0]) - .Transforms().WithMappingActions(new [] { action }) + .Transforms().WithMappingActions(new[] { action }) .Properties(); Assert.That(testee.MappingActions, Is.EqualTo(new[] { action })); @@ -96,9 +99,9 @@ public void MappingActionOnly_Usage() var action = new Action((tgt, src) => called = true); var testee = new TypeMappingSpec() - .Transforms().WithTargetProperties(new PropertyInfo[0]) - .Transforms().WithSourceProperties(new PropertyInfo[0]) - .Transforms().WithMappingActions(new[] {action}) + .Transforms().WithTargetProperties(new PropertyInfo[0]) + .Transforms().WithSourceProperties(new PropertyInfo[0]) + .Transforms().WithMappingActions(new[] { action }) ; var mapper = testee.Create(); @@ -130,4 +133,4 @@ public sealed class Target public int Prop4 { get; set; } } } -} +} \ No newline at end of file diff --git a/Samples/FluentMapper/CreateEmptyAssembler.cs b/Samples/FluentMapper/CreateEmptyAssembler.cs index 084982de..350720b4 100644 --- a/Samples/FluentMapper/CreateEmptyAssembler.cs +++ b/Samples/FluentMapper/CreateEmptyAssembler.cs @@ -12,4 +12,4 @@ public TTgt Assemble(TSrc source, Action mappingAction) return DefaultAssembler.Assemble(source, mappingAction); } } -} +} \ No newline at end of file diff --git a/Samples/FluentMapper/DefaultAssembler.cs b/Samples/FluentMapper/DefaultAssembler.cs index 5d93d1ea..adb49a9b 100644 --- a/Samples/FluentMapper/DefaultAssembler.cs +++ b/Samples/FluentMapper/DefaultAssembler.cs @@ -6,14 +6,14 @@ internal sealed class DefaultAssembler : IAssembler { TTgt IAssembler.Assemble(TSrc source, Action mappingAction) { - return DefaultAssembler.Assemble(source, mappingAction); + return Assemble(source, mappingAction); } public static TTgt Assemble(TSrc source, Action mappingAction) - { + { if (source == null) throw new ArgumentNullException("source", $"Cannot map instance of {typeof(TTgt).Name}" + - $" from null instance of {typeof(TSrc).Name}."); + $" from null instance of {typeof(TSrc).Name}."); var target = (TTgt)Activator.CreateInstance(typeof(TTgt)); @@ -22,4 +22,4 @@ public static TTgt Assemble(TSrc source, Action mappingAction) return target; } } -} +} \ No newline at end of file diff --git a/Samples/FluentMapper/FluentMapper.cs b/Samples/FluentMapper/FluentMapper.cs index a66f2dbe..06e17cba 100644 --- a/Samples/FluentMapper/FluentMapper.cs +++ b/Samples/FluentMapper/FluentMapper.cs @@ -7,4 +7,4 @@ public static TargetTypeSpec ThatMaps() return new TargetTypeSpec(); } } -} +} \ No newline at end of file diff --git a/Samples/FluentMapper/FluentMapping.csproj b/Samples/FluentMapper/FluentMapping.csproj index d7f3a084..7a356ce3 100644 --- a/Samples/FluentMapper/FluentMapping.csproj +++ b/Samples/FluentMapper/FluentMapping.csproj @@ -1,9 +1,9 @@  - - 8 - netcoreapp3.1 - false - + + 8 + netcoreapp3.1 + false + diff --git a/Samples/FluentMapper/IAssembler.cs b/Samples/FluentMapper/IAssembler.cs index 0f982430..ef35ce75 100644 --- a/Samples/FluentMapper/IAssembler.cs +++ b/Samples/FluentMapper/IAssembler.cs @@ -6,4 +6,4 @@ public interface IAssembler { TTgt Assemble(TSrc source, Action mappingAction); } -} +} \ No newline at end of file diff --git a/Samples/FluentMapper/Internal/TypeMappingSpecExtensions.cs b/Samples/FluentMapper/Internal/TypeMappingSpecExtensions.cs index 8eb45bcf..c88559a6 100644 --- a/Samples/FluentMapper/Internal/TypeMappingSpecExtensions.cs +++ b/Samples/FluentMapper/Internal/TypeMappingSpecExtensions.cs @@ -2,16 +2,16 @@ { public static class TypeMappingSpecExtensions { - public static ITypeMappingSpecProperties + public static ITypeMappingSpecProperties Properties(this TypeMappingSpec spec) { return spec; } - public static ITypeMappingSpecTransforms + public static ITypeMappingSpecTransforms Transforms(this TypeMappingSpec spec) { return spec; } } -} +} \ No newline at end of file diff --git a/Samples/FluentMapper/NullSourceBehavior.cs b/Samples/FluentMapper/NullSourceBehavior.cs index d859712a..f2c36a3d 100644 --- a/Samples/FluentMapper/NullSourceBehavior.cs +++ b/Samples/FluentMapper/NullSourceBehavior.cs @@ -10,15 +10,15 @@ public interface INullSourceBehaviorState public sealed class NullSourceBehavior : INullSourceBehaviorState { - TypeMappingSpec INullSourceBehaviorState.Spec => _spec; - - private TypeMappingSpec _spec; + private readonly TypeMappingSpec _spec; public NullSourceBehavior(TypeMappingSpec spec) { _spec = spec; } + TypeMappingSpec INullSourceBehaviorState.Spec => _spec; + public TypeMappingSpec ReturnNull() { return _spec.Transforms() diff --git a/Samples/FluentMapper/ReturnNullAssembler.cs b/Samples/FluentMapper/ReturnNullAssembler.cs index f868f5e8..2f08dc0d 100644 --- a/Samples/FluentMapper/ReturnNullAssembler.cs +++ b/Samples/FluentMapper/ReturnNullAssembler.cs @@ -7,9 +7,9 @@ public sealed class ReturnNullAssembler : IAssembler public TTgt Assemble(TSrc source, Action mappingAction) { if (source == null) - return default(TTgt); + return default; return DefaultAssembler.Assemble(source, mappingAction); } } -} +} \ No newline at end of file diff --git a/Samples/FluentMapper/SetterSpec.cs b/Samples/FluentMapper/SetterSpec.cs index 0cd7ac48..ac3a151b 100644 --- a/Samples/FluentMapper/SetterSpec.cs +++ b/Samples/FluentMapper/SetterSpec.cs @@ -15,23 +15,28 @@ public interface ISetterSpecProperties public sealed class SetterSpec : ISetterSpecProperties { - private TypeMappingSpec _spec; - private PropertyInfo _targetProperty; + private readonly TypeMappingSpec _spec; + private readonly PropertyInfo _targetProperty; public SetterSpec( TypeMappingSpec spec, PropertyInfo targetProperty - ) + ) { _spec = spec; _targetProperty = targetProperty; } + TypeMappingSpec ISetterSpecProperties.Spec => _spec; + + PropertyInfo ISetterSpecProperties.TargetProperty => _targetProperty; + public TypeMappingSpec From(Expression> propertyExpression) { var srcParam = Expression.Parameter(typeof(TSrc)); var tgtParam = Expression.Parameter(typeof(TTgt)); - var srcExpr = Expression.Property(srcParam, ((MemberExpression)propertyExpression.Body).Member as PropertyInfo); + var srcExpr = Expression.Property(srcParam, + ((MemberExpression)propertyExpression.Body).Member as PropertyInfo); var tgtSetterInfo = _targetProperty.GetSetMethod(); var tgtSetterExpr = Expression.Call(tgtParam, tgtSetterInfo, srcExpr); @@ -41,26 +46,22 @@ public TypeMappingSpec From(Expression x != srcExpr.Member) - .ToArray() - ) - .Transforms().WithTargetProperties( - specProperties.TargetProperties - .Where(x => x != _targetProperty) - .ToArray() - ) + .Transforms().WithMappingActions( + specProperties.MappingActions + .Concat(new[] { setterAction }) + .ToArray() + ) + .Transforms().WithSourceProperties( + specProperties.SourceProperties + .Where(x => x != srcExpr.Member) + .ToArray() + ) + .Transforms().WithTargetProperties( + specProperties.TargetProperties + .Where(x => x != _targetProperty) + .ToArray() + ) ; } - - TypeMappingSpec ISetterSpecProperties.Spec => _spec; - - PropertyInfo ISetterSpecProperties.TargetProperty => _targetProperty; } } \ No newline at end of file diff --git a/Samples/FluentMapper/TypeMappingSpec.cs b/Samples/FluentMapper/TypeMappingSpec.cs index d2e25545..48b4e0c3 100644 --- a/Samples/FluentMapper/TypeMappingSpec.cs +++ b/Samples/FluentMapper/TypeMappingSpec.cs @@ -8,19 +8,19 @@ namespace FluentMapping { public sealed class TypeMappingSpec : ITypeMappingSpecProperties, - ITypeMappingSpecTransforms + ITypeMappingSpecTransforms { + private readonly IAssembler _assembler; private readonly IEnumerable> _mappingActions; private readonly IEnumerable _srcProperties; private readonly IEnumerable _tgtProperties; - private readonly IAssembler _assembler; public TypeMappingSpec() : this( - typeof(TTgt).GetProperties(), - typeof(TSrc).GetProperties(), - new Action[0], - new DefaultAssembler()) + typeof(TTgt).GetProperties(), + typeof(TSrc).GetProperties(), + new Action[0], + new DefaultAssembler()) { } @@ -29,7 +29,7 @@ private TypeMappingSpec( IEnumerable sourceProperties, IEnumerable> mappingActions, IAssembler assembler - ) + ) { _mappingActions = mappingActions; _tgtProperties = targetProperties; @@ -37,11 +37,6 @@ IAssembler assembler _assembler = assembler; } - public NullSourceBehavior WithNullSource() - { - return new NullSourceBehavior(this); - } - IEnumerable ITypeMappingSpecProperties.SourceProperties => _srcProperties; IEnumerable ITypeMappingSpecProperties.TargetProperties => _tgtProperties; IEnumerable> ITypeMappingSpecProperties.MappingActions => _mappingActions; @@ -55,7 +50,7 @@ TypeMappingSpec ITypeMappingSpecTransforms sourceProperties, _mappingActions, _assembler - ); + ); } TypeMappingSpec ITypeMappingSpecTransforms @@ -66,7 +61,7 @@ TypeMappingSpec ITypeMappingSpecTransforms _srcProperties, _mappingActions, _assembler - ); + ); } TypeMappingSpec ITypeMappingSpecTransforms @@ -77,7 +72,7 @@ TypeMappingSpec ITypeMappingSpecTransforms _srcProperties, mappingActions, _assembler - ); + ); } TypeMappingSpec ITypeMappingSpecTransforms @@ -88,7 +83,12 @@ TypeMappingSpec ITypeMappingSpecTransforms _srcProperties, _mappingActions, assembler - ); + ); + } + + public NullSourceBehavior WithNullSource() + { + return new NullSourceBehavior(this); } public IMapper Create() @@ -96,11 +96,11 @@ public IMapper Create() var unmappedTargets = _tgtProperties .Where(tgtProp => !_srcProperties .Any(srcProp => srcProp.Name == tgtProp.Name) - ); + ); var unmappedSources = _srcProperties .Where(srcProp => !_tgtProperties .Any(tgtProp => tgtProp.Name == srcProp.Name) - ); + ); if (unmappedSources.Any() || unmappedTargets.Any()) { @@ -110,7 +110,8 @@ public IMapper Create() .Select(x => typeof(TSrc).Name + "." + x.Name); var exceptionMessage = "Unmapped properties: " - + string.Join(", ", targetStrings.OrderBy(x => x).Concat(srcStrings.OrderBy(x => x))); + + string.Join(", ", + targetStrings.OrderBy(x => x).Concat(srcStrings.OrderBy(x => x))); throw new Exception(exceptionMessage); } @@ -138,16 +139,16 @@ public IMapper Create() actions.AddRange(_mappingActions .Select(ToExpression) .Select(x => Expression.Invoke(x, tgtParam, srcParam)) - ); + ); var setterCallsExpression = Expression.Block(actions.ToArray()); var compiledAction = Expression.Lambda>( - setterCallsExpression, - tgtParam, - srcParam - ) - .Compile() + setterCallsExpression, + tgtParam, + srcParam + ) + .Compile() ; return new Mapper(compiledAction, _assembler); @@ -155,40 +156,40 @@ public IMapper Create() public SetterSpec ThatSets( Expression> propertyExpression - ) + ) { var memberInfo = ((MemberExpression)propertyExpression.Body).Member; return new SetterSpec(this, (PropertyInfo)memberInfo); } - public TypeMappingSpec + public TypeMappingSpec IgnoringTargetProperty(Expression> propertyExpression) { var propInfo = GetPropertyInfo(propertyExpression, nameof(IgnoringTargetProperty), "tgt"); - var targetProperties = this._tgtProperties.Where(x => !x.Equals(propInfo)); + var targetProperties = _tgtProperties.Where(x => !x.Equals(propInfo)); return new TypeMappingSpec( targetProperties.ToArray(), _srcProperties, _mappingActions, _assembler - ); + ); } public TypeMappingSpec IgnoringSourceProperty(Expression> propertyExpression) { var propInfo = GetPropertyInfo(propertyExpression, nameof(IgnoringSourceProperty), "src"); - var sourceProperties = this._srcProperties.Where(x => !x.Equals(propInfo)); + var sourceProperties = _srcProperties.Where(x => !x.Equals(propInfo)); return new TypeMappingSpec( _tgtProperties, sourceProperties.ToArray(), _mappingActions, _assembler - ); + ); } private PropertyInfo GetPropertyInfo( @@ -204,8 +205,9 @@ private PropertyInfo GetPropertyInfo( if (propInfo != null) return propInfo; } + throw new Exception($"{methodName}(...) requires an expression " - + $"that is a simple property access of the form '{paramName} => {paramName}.Property'."); + + $"that is a simple property access of the form '{paramName} => {paramName}.Property'."); } private Expression> ToExpression(Action delegateInstance) @@ -215,8 +217,8 @@ private Expression> ToExpression(Action delegateI private sealed class Mapper : IMapper { - private readonly Action _mappingAction; private readonly IAssembler _assembler; + private readonly Action _mappingAction; public Mapper(Action mappingAction, IAssembler assembler) { diff --git a/Samples/Spectacle/Program.cs b/Samples/Spectacle/Program.cs index 33717d2b..39ee157e 100644 --- a/Samples/Spectacle/Program.cs +++ b/Samples/Spectacle/Program.cs @@ -11,7 +11,7 @@ public static void Main() .Delay(TimeSpan.FromSeconds(1)) .UntilKeyPressed(s => s.TypeText("тра-ля-ля") - .TypeText("тру-лю-лю") + .TypeText("тру-лю-лю") ) .Say("Пока-пока!"); diff --git a/Samples/Spectacle/Spectacle.cs b/Samples/Spectacle/Spectacle.cs index aa7fbc75..4d76a7b6 100644 --- a/Samples/Spectacle/Spectacle.cs +++ b/Samples/Spectacle/Spectacle.cs @@ -27,10 +27,7 @@ public Spectacle Delay(TimeSpan timeSpan) public void Play() { - foreach (var action in actions) - { - action(); - } + foreach (var action in actions) action(); } public Spectacle UntilKeyPressed(Func inner) @@ -38,10 +35,7 @@ public Spectacle UntilKeyPressed(Func inner) var innerSpectacle = inner(new Spectacle()); Schedule(() => { - while (!Console.KeyAvailable) - { - innerSpectacle.Play(); - } + while (!Console.KeyAvailable) innerSpectacle.Play(); Console.ReadKey(true); }); return this; diff --git a/Samples/Spectacle/Spectacle.csproj b/Samples/Spectacle/Spectacle.csproj index 6fcb3e8a..9c32c3bb 100644 --- a/Samples/Spectacle/Spectacle.csproj +++ b/Samples/Spectacle/Spectacle.csproj @@ -1,12 +1,12 @@  - - 8 - netcoreapp3.1 - Exe - SpectacleSample - SpectacleSample - false - + + 8 + netcoreapp3.1 + Exe + SpectacleSample + SpectacleSample + false + diff --git a/Samples/Spectacle/SpectacleExtensions.cs b/Samples/Spectacle/SpectacleExtensions.cs index 24e6b740..9ebe441f 100644 --- a/Samples/Spectacle/SpectacleExtensions.cs +++ b/Samples/Spectacle/SpectacleExtensions.cs @@ -14,6 +14,7 @@ public static Spectacle TypeText(this Spectacle spectacle, string message) Console.Write(ch); Thread.Sleep(50); } + Console.WriteLine(); }); return spectacle; diff --git a/StringSerializer.Tests/StringSerializer.Tests.csproj b/StringSerializer.Tests/StringSerializer.Tests.csproj new file mode 100644 index 00000000..4b2956fd --- /dev/null +++ b/StringSerializer.Tests/StringSerializer.Tests.csproj @@ -0,0 +1,21 @@ + + + + net7.0 + disable + enable + + false + true + + + + + + + + + + + + \ No newline at end of file diff --git a/StringSerializer.Tests/StringSerializerTests.cs b/StringSerializer.Tests/StringSerializerTests.cs new file mode 100644 index 00000000..301f77a7 --- /dev/null +++ b/StringSerializer.Tests/StringSerializerTests.cs @@ -0,0 +1,213 @@ +using NUnit.Framework; +using ObjectPrinting; +using StringSerializer.Tests.TestModels; +using System; +using System.Collections.Generic; +using System.Globalization; + +namespace StringSerializer.Tests; + +[TestFixture] +public class StringSerializerTests +{ + [SetUp] + public void SetUp() + { + testObject = new TestObject + { + IntNumber = random.Next(1, 1001), + DoubleNumber = random.NextDouble(), + Date = new DateTime(random.Next(1901, 2070), random.Next(1, 13), random.Next(1, 26)), + Line = new string('_', random.Next(1, 100)) + }; + serializer = new StringSerializer(); + } + + [OneTimeSetUp] + public void OneTimeSetUp() + { + random = new Random(); + } + + private TestObject testObject; + private StringSerializer serializer; + private Random random; + + [Test] + public void Serializer_Demo() + { + testObject.Dict = new Dictionary + { + [0] = new(), + [1] = new(), + [2] = new() + }; + + var serialized = serializer + .Ignoring() + .Ignoring(obj => obj.IntNumber) + .ChangeSerializingFor(obj => obj.Line, line => $"<{line}>") + .ChangeSerializingFor(num => num.ToString(new CultureInfo("en"))) + .Serialize(testObject); + + Console.WriteLine(serialized); + Assert.Pass(); + } + + [Test] + public void Serializer_Should_IgnoreTypes() + { + var actual = serializer + .Ignoring() + .Ignoring() + .Ignoring() + .Ignoring() + .Ignoring() + .Ignoring>() + .Ignoring>() + .Ignoring>() + .Serialize(testObject); + + Assert.AreEqual(actual, nameof(TestObject) + Environment.NewLine); + } + + [Test] + public void Serializer_Should_IgnoreMembers() + { + var actual = serializer + .Ignoring(test => test.IntNumber) + .Ignoring(test => test.DoubleNumber) + .Ignoring(test => test.Line!) + .Ignoring(test => test.Date) + .Ignoring(test => test.CircularReference!) + .Ignoring(test => test.Dict!) + .Ignoring(test => test.List) + .Ignoring(test => test.InnerClasses) + .Serialize(testObject); + + Assert.AreEqual(actual, nameof(TestObject) + Environment.NewLine); + } + + [TestCase("fr-FR")] + [TestCase("ja-JP")] + [TestCase("en-US")] + [TestCase("ar-SA")] + public void Serializer_Should_SupportCultureSetting(string signature) + { + var culture = new CultureInfo(signature); + + var actual = serializer + .ChangeSerializingFor(date => date.ToString(culture)) + .ChangeSerializingFor(num => num.ToString(culture)) + .Serialize(testObject); + + StringAssert.Contains( + $"{nameof(TestObject.DoubleNumber)} = {testObject.DoubleNumber.ToString(culture)}", actual); + StringAssert.Contains( + $"{nameof(TestObject.Date)} = {testObject.Date.ToString(culture)}", actual); + } + + [Test] + public void Serializer_Should_SupportLineTrimming() + { + var cutLength = random.Next(10, 70); + var actual = serializer + .TrimLinesTo(cutLength) + .Serialize(testObject); + + var expected = $"{nameof(TestObject.Line)} = {testObject.Line}"; + + if (cutLength <= expected.Length) + expected = expected[..cutLength]; + + StringAssert.Contains(expected, actual); + } + + [Test] + public void Serializer_Should_SupportCustomSerializationForMembers() + { + var actual = serializer + .ChangeSerializingFor(obj => obj.DoubleNumber, num => $"-> {num} <-") + .ChangeSerializingFor(obj => obj.Line, _ => string.Empty) + .Serialize(testObject); + + StringAssert.Contains( + $"{nameof(TestObject.DoubleNumber)} = -> {testObject.DoubleNumber} <-", actual); + StringAssert.Contains( + $"{nameof(TestObject.CircularReference)} = {string.Empty}", actual); + } + + [Test] + public void Serializer_Should_SupportCustomSerializationForTypes() + { + var actual = serializer + .ChangeSerializingFor(num => $"-> {num} <-") + .ChangeSerializingFor(_ => string.Empty) + .Serialize(testObject); + + StringAssert.Contains( + $"{nameof(TestObject.DoubleNumber)} = -> {testObject.DoubleNumber} <-", actual); + StringAssert.Contains( + $"{nameof(TestObject.CircularReference)} = {string.Empty}", actual); + } + + [Test] + public void Serializer_Should_DetectCircularReferences() + { + testObject.CircularReference = testObject; + serializer.Serialize(testObject); + Assert.Pass(); + } + + [Test] + public void Serializer_Should_SerializeFinalTypesCorrectly() + { + var finalTypes = new[] + { + typeof(int), typeof(double), typeof(float), typeof(string), + typeof(DateTime), typeof(TimeSpan) + }; + + foreach (var type in finalTypes) + { + var instance = type == typeof(string) ? "Hello, world!" : Activator.CreateInstance(type); + Assert.That(instance!.ToString(), Is.EqualTo(serializer.Serialize(instance))); + } + } + + [Test] + public void Serializer_Should_StopAtFinalTypes() + { + var actual = serializer + .Ignoring>() + .Ignoring() + .Ignoring>() + .Ignoring>() + .Serialize(testObject); + + // \t\t will appear in actual only if final type will be opened. + StringAssert.DoesNotContain("\t\t", actual); + } + + [Test] + public void Serializer_Should_WorkWithCollections() + { + var list = new List { 1, 2, 3 }; + var actual = serializer.Serialize(list); + + Assert.AreEqual("[\n 1,\n 2,\n 3,\n]", actual); + } + + [Test] + public void Serializer_Should_WorkWithDictionaries() + { + var dict = new Dictionary> + { + [0] = new() { [1] = "One" }, + [1] = new() { [2] = "Two" } + }; + + const string expected = "[\n [0] => [\n\t [1] => One,\n\t],\n [1] => [\n\t [2] => Two,\n\t],\n]"; + Assert.AreEqual(expected, serializer.Serialize(dict)); + } +} \ No newline at end of file diff --git a/StringSerializer.Tests/TestModels/TestObject.cs b/StringSerializer.Tests/TestModels/TestObject.cs new file mode 100644 index 00000000..3dec9857 --- /dev/null +++ b/StringSerializer.Tests/TestModels/TestObject.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; + +namespace StringSerializer.Tests.TestModels; + +public class TestObject +{ + public int IntNumber { get; init; } + public double DoubleNumber { get; init; } + public string? Line { get; init; } + public DateTime Date { get; init; } + public TestObject? CircularReference { get; set; } + + public Dictionary? Dict { get; set; } + + public List List { get; } = new() { "Echo", "Lima", "Omega", "Bravo", "Delta" }; + + public List InnerClasses { get; } = new() + { + new InnerClass { StringField = "String value", IntField = 42 }, + new InnerClass { StringField = "String value too", IntField = 84 } + }; + + public class InnerClass + { + public string StringField { get; set; } + public int IntField { get; set; } + } +} \ No newline at end of file diff --git a/fluent-api.sln b/fluent-api.sln index 69c8db9e..b38970fe 100644 --- a/fluent-api.sln +++ b/fluent-api.sln @@ -13,6 +13,10 @@ 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}") = "StringSerializer.Tests", "StringSerializer.Tests\StringSerializer.Tests.csproj", "{DC4FD21E-9E2A-4D14-8CA0-6B9B259F1796}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ObjectPrinting", "ObjectPrinting", "{2EAE1C00-4432-4418-A7E6-7908E5C5D694}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -24,17 +28,21 @@ Global {07B8C9B7-8289-46CB-9875-048A57758EEE}.Release|Any CPU.ActiveCfg = Release|Any CPU {07B8C9B7-8289-46CB-9875-048A57758EEE}.Release|Any CPU.Build.0 = Release|Any CPU {FEEA5AFE-459A-4D13-81D0-252E1A2E6F4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FEEA5AFE-459A-4D13-81D0-252E1A2E6F4E}.Debug|Any CPU.Build.0 = Debug|Any CPU {FEEA5AFE-459A-4D13-81D0-252E1A2E6F4E}.Release|Any CPU.ActiveCfg = Release|Any CPU {FEEA5AFE-459A-4D13-81D0-252E1A2E6F4E}.Release|Any CPU.Build.0 = Release|Any CPU + {FEEA5AFE-459A-4D13-81D0-252E1A2E6F4E}.Debug|Any CPU.Build.0 = Debug|Any CPU {8A7BB3EA-3E6A-4D04-A801-D5CD1620DA0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8A7BB3EA-3E6A-4D04-A801-D5CD1620DA0D}.Debug|Any CPU.Build.0 = Debug|Any CPU {8A7BB3EA-3E6A-4D04-A801-D5CD1620DA0D}.Release|Any CPU.ActiveCfg = Release|Any CPU {8A7BB3EA-3E6A-4D04-A801-D5CD1620DA0D}.Release|Any CPU.Build.0 = Release|Any CPU + {8A7BB3EA-3E6A-4D04-A801-D5CD1620DA0D}.Debug|Any CPU.Build.0 = Debug|Any CPU {EFA9335C-411B-4597-B0B6-5438D1AE04C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {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 + {EFA9335C-411B-4597-B0B6-5438D1AE04C3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DC4FD21E-9E2A-4D14-8CA0-6B9B259F1796}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DC4FD21E-9E2A-4D14-8CA0-6B9B259F1796}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DC4FD21E-9E2A-4D14-8CA0-6B9B259F1796}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DC4FD21E-9E2A-4D14-8CA0-6B9B259F1796}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -43,5 +51,7 @@ Global {FEEA5AFE-459A-4D13-81D0-252E1A2E6F4E} = {6D308E4A-CEC7-4536-9B87-81CD337A87AD} {8A7BB3EA-3E6A-4D04-A801-D5CD1620DA0D} = {6D308E4A-CEC7-4536-9B87-81CD337A87AD} {EFA9335C-411B-4597-B0B6-5438D1AE04C3} = {6D308E4A-CEC7-4536-9B87-81CD337A87AD} + {07B8C9B7-8289-46CB-9875-048A57758EEE} = {2EAE1C00-4432-4418-A7E6-7908E5C5D694} + {DC4FD21E-9E2A-4D14-8CA0-6B9B259F1796} = {2EAE1C00-4432-4418-A7E6-7908E5C5D694} EndGlobalSection EndGlobal