From dafc94f8e2682b528aea45163f23e5df850270db Mon Sep 17 00:00:00 2001 From: Mike-E Date: Sat, 21 Nov 2020 03:14:53 -0500 Subject: [PATCH 1/2] Adjusted support for nullable structure types. --- .../ContentModel/Content/NullableContents.cs | 24 ---- .../ContentModel/Members/MemberSerializers.cs | 3 +- .../ExtensionModel/Content/Contents.cs | 3 - .../Members/ParameterizedResultHandler.cs | 7 +- .../ExtensionModel/DefaultExtensions.cs | 1 + .../ExtensionModel/SerializationExtension.cs | 1 - .../Types/NullableStructureAwareExtension.cs | 33 ++++++ .../Xml/AutoMemberFormatExtension.cs | 8 +- .../IsNullableTypeSpecification.cs | 12 -- .../Issue477Tests.cs | 92 ++++++++++++++ .../Xml/ExtendedXmlSerializerTests.cs | 112 ++++++++++++------ 11 files changed, 208 insertions(+), 88 deletions(-) delete mode 100644 src/ExtendedXmlSerializer/ContentModel/Content/NullableContents.cs create mode 100644 src/ExtendedXmlSerializer/ExtensionModel/Types/NullableStructureAwareExtension.cs delete mode 100644 src/ExtendedXmlSerializer/ReflectionModel/IsNullableTypeSpecification.cs create mode 100644 test/ExtendedXmlSerializer.Tests.ReportedIssues/Issue477Tests.cs diff --git a/src/ExtendedXmlSerializer/ContentModel/Content/NullableContents.cs b/src/ExtendedXmlSerializer/ContentModel/Content/NullableContents.cs deleted file mode 100644 index 6427bc9cb..000000000 --- a/src/ExtendedXmlSerializer/ContentModel/Content/NullableContents.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Reflection; -using ExtendedXmlSerializer.ContentModel.Conversion; -using ExtendedXmlSerializer.Core; -using ExtendedXmlSerializer.Core.Sources; -using JetBrains.Annotations; - -namespace ExtendedXmlSerializer.ContentModel.Content -{ - sealed class NullableContents : DelegatedSource, IContents - { - [UsedImplicitly] - public NullableContents(ConverterContents converters) : base(converters.In(Alteration.Default) - .Get) {} - - sealed class Alteration : IAlteration - { - public static Alteration Default { get; } = new Alteration(); - - Alteration() {} - - public TypeInfo Get(TypeInfo parameter) => parameter.AccountForNullable(); - } - } -} \ No newline at end of file diff --git a/src/ExtendedXmlSerializer/ContentModel/Members/MemberSerializers.cs b/src/ExtendedXmlSerializer/ContentModel/Members/MemberSerializers.cs index 4d4707f05..cfdf26010 100644 --- a/src/ExtendedXmlSerializer/ContentModel/Members/MemberSerializers.cs +++ b/src/ExtendedXmlSerializer/ContentModel/Members/MemberSerializers.cs @@ -60,8 +60,7 @@ bool IsMember(IMember profile) IMemberSerializer Content(IMember profile, IMemberAccess access) { var identity = new Identity(profile); - var isMember = IsMember(profile); - var composite = isMember + var composite = IsMember(profile) ? (IWriter)new MemberPropertyWriter(identity) : identity; var start = composite.Adapt(); diff --git a/src/ExtendedXmlSerializer/ExtensionModel/Content/Contents.cs b/src/ExtendedXmlSerializer/ExtensionModel/Content/Contents.cs index dcebae89c..c3693bafb 100644 --- a/src/ExtendedXmlSerializer/ExtensionModel/Content/Contents.cs +++ b/src/ExtendedXmlSerializer/ExtensionModel/Content/Contents.cs @@ -46,9 +46,6 @@ public IServiceRepository Get(IServiceRepository parameter) .DecorateContentsWith() .When(ReflectionContentSpecification.Default) - .DecorateContentsWith() - .When(IsNullableTypeSpecification.Default) - .DecorateContentsWith() .When() .DecorateContentsWith() diff --git a/src/ExtendedXmlSerializer/ExtensionModel/Content/Members/ParameterizedResultHandler.cs b/src/ExtendedXmlSerializer/ExtensionModel/Content/Members/ParameterizedResultHandler.cs index 3b4bced52..c550f749b 100644 --- a/src/ExtendedXmlSerializer/ExtensionModel/Content/Members/ParameterizedResultHandler.cs +++ b/src/ExtendedXmlSerializer/ExtensionModel/Content/Members/ParameterizedResultHandler.cs @@ -7,12 +7,9 @@ sealed class ParameterizedResultHandler : IInnerContentResult { readonly IInnerContentResult _result; - public ParameterizedResultHandler(IInnerContentResult result) - { - _result = result; - } + public ParameterizedResultHandler(IInnerContentResult result) => _result = result; public object Get(IInnerContent parameter) - => (parameter.Current as IActivationContext)?.Get() ?? _result.Get(parameter); + => parameter.Current is IActivationContext context ? context.Get() : _result.Get(parameter); } } \ No newline at end of file diff --git a/src/ExtendedXmlSerializer/ExtensionModel/DefaultExtensions.cs b/src/ExtendedXmlSerializer/ExtensionModel/DefaultExtensions.cs index 406500569..94deaf682 100644 --- a/src/ExtendedXmlSerializer/ExtensionModel/DefaultExtensions.cs +++ b/src/ExtendedXmlSerializer/ExtensionModel/DefaultExtensions.cs @@ -60,6 +60,7 @@ public override IEnumerator GetEnumerator() yield return new MemberFormatExtension(); yield return ImmutableArrayExtension.Default; yield return SerializationExtension.Default; + yield return NullableStructureAwareExtension.Default; yield return new CustomSerializationExtension(); yield return CachingExtension.Default; } diff --git a/src/ExtendedXmlSerializer/ExtensionModel/SerializationExtension.cs b/src/ExtendedXmlSerializer/ExtensionModel/SerializationExtension.cs index 8feacb654..0ae5bdf7c 100644 --- a/src/ExtendedXmlSerializer/ExtensionModel/SerializationExtension.cs +++ b/src/ExtendedXmlSerializer/ExtensionModel/SerializationExtension.cs @@ -32,7 +32,6 @@ public IServiceRepository Get(IServiceRepository parameter) .Register() .Register() .RegisterInstance(RuntimeSerializationExceptionMessage.Default) - .Decorate() .Decorate() .Decorate() .Decorate(); diff --git a/src/ExtendedXmlSerializer/ExtensionModel/Types/NullableStructureAwareExtension.cs b/src/ExtendedXmlSerializer/ExtensionModel/Types/NullableStructureAwareExtension.cs new file mode 100644 index 000000000..557e9eef2 --- /dev/null +++ b/src/ExtendedXmlSerializer/ExtensionModel/Types/NullableStructureAwareExtension.cs @@ -0,0 +1,33 @@ +using ExtendedXmlSerializer.ContentModel; +using ExtendedXmlSerializer.ContentModel.Content; +using System; +using System.Reflection; + +namespace ExtendedXmlSerializer.ExtensionModel.Types +{ + sealed class NullableStructureAwareExtension : ISerializerExtension + { + public static NullableStructureAwareExtension Default { get; } = new NullableStructureAwareExtension(); + + NullableStructureAwareExtension() {} + + public IServiceRepository Get(IServiceRepository parameter) + => parameter.DecorateContentsWith().Then(); + + public void Execute(IServices parameter) {} + + sealed class NullableStructureAwareContents : IContents + { + readonly IContents _previous; + + public NullableStructureAwareContents(IContents previous) => _previous = previous; + + public ISerializer Get(TypeInfo parameter) + { + var underlying = Nullable.GetUnderlyingType(parameter); + var serializer = _previous.Get(underlying != null ? underlying : parameter); + return serializer; + } + } + } +} diff --git a/src/ExtendedXmlSerializer/ExtensionModel/Xml/AutoMemberFormatExtension.cs b/src/ExtendedXmlSerializer/ExtensionModel/Xml/AutoMemberFormatExtension.cs index 9bf0f1272..ebe9452b1 100644 --- a/src/ExtendedXmlSerializer/ExtensionModel/Xml/AutoMemberFormatExtension.cs +++ b/src/ExtendedXmlSerializer/ExtensionModel/Xml/AutoMemberFormatExtension.cs @@ -1,8 +1,8 @@ -using System.Reflection; using ExtendedXmlSerializer.ContentModel.Conversion; using ExtendedXmlSerializer.ContentModel.Members; using ExtendedXmlSerializer.Core; using ExtendedXmlSerializer.Core.Specifications; +using System.Reflection; namespace ExtendedXmlSerializer.ExtensionModel.Xml { @@ -48,11 +48,7 @@ public MemberConverters(IMemberConverters members, IConverters converters) _converters = converters; } - public IConverter Get(MemberInfo parameter) - { - var converter = _members.Get(parameter); - return converter ?? From(parameter); - } + public IConverter Get(MemberInfo parameter) => _members.Get(parameter) ?? From(parameter); IConverter From(MemberDescriptor parameter) => _converters.Get(parameter.MemberType.AccountForNullable()); } diff --git a/src/ExtendedXmlSerializer/ReflectionModel/IsNullableTypeSpecification.cs b/src/ExtendedXmlSerializer/ReflectionModel/IsNullableTypeSpecification.cs deleted file mode 100644 index 56c4c8dea..000000000 --- a/src/ExtendedXmlSerializer/ReflectionModel/IsNullableTypeSpecification.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using ExtendedXmlSerializer.Core.Specifications; - -namespace ExtendedXmlSerializer.ReflectionModel -{ - sealed class IsNullableTypeSpecification : DelegatedAssignedSpecification - { - public static IsNullableTypeSpecification Default { get; } = new IsNullableTypeSpecification(); - - IsNullableTypeSpecification() : base(Nullable.GetUnderlyingType) {} - } -} \ No newline at end of file diff --git a/test/ExtendedXmlSerializer.Tests.ReportedIssues/Issue477Tests.cs b/test/ExtendedXmlSerializer.Tests.ReportedIssues/Issue477Tests.cs new file mode 100644 index 000000000..ae5c5bdf2 --- /dev/null +++ b/test/ExtendedXmlSerializer.Tests.ReportedIssues/Issue477Tests.cs @@ -0,0 +1,92 @@ +using ExtendedXmlSerializer.Configuration; +using ExtendedXmlSerializer.Tests.ReportedIssues.Support; +using FluentAssertions; +using System; +using Xunit; + +#nullable enable +namespace ExtendedXmlSerializer.Tests.ReportedIssues +{ + public sealed class Issue477Tests + { + [Fact] + public void SerializeAndDeserializeInstanceA() + { + var serializer = new ConfigurationContainer().EnableParameterizedContentWithPropertyAssignments() + .Create() + .ForTesting(); + + var instance = new A { Point = new Point(2, 3) }; + + serializer.Cycle(instance).Should().BeEquivalentTo(instance); + } + + [Fact] + public void SerializeAndDeserializeInstanceB() + { + var serializer = new ConfigurationContainer().EnableParameterizedContent() + .Create() + .ForTesting(); + + var instance = new B { NullablePoint = new Point(24, 7) }; + + serializer.Cycle(instance).Should().BeEquivalentTo(instance); + } + + [Fact] + public void SerializeAndDeserializeInstanceBNull() + { + var serializer = new ConfigurationContainer().EnableParameterizedContent() + .Create() + .ForTesting(); + + var instance = new B { NullablePoint = null }; + + serializer.Assert(instance, @""); + + serializer.Cycle(instance).Should().BeEquivalentTo(instance); + } + + public sealed class A + { + public Point Point { get; set; } + } + + public sealed class B + { + public Point? NullablePoint { get; set; } + } + + public readonly struct Point : IEquatable + { + public Point(int x, int y) + { + X = x; + Y = y; + } + + public int X { get; } + + public int Y { get; } + + public bool Equals(Point other) => X == other.X && Y == other.Y; + + public override bool Equals(object? obj) => obj is Point other && Equals(other); + + public override int GetHashCode() + { + unchecked + { + return (X * 397) ^ Y; + } + } + + public override string ToString() => $"({X}, {Y})"; + + public static bool operator ==(Point left, Point right) => left.Equals(right); + + public static bool operator !=(Point left, Point right) => !left.Equals(right); + } + } +} +#nullable restore \ No newline at end of file diff --git a/test/ExtendedXmlSerializer.Tests/ExtensionModel/Xml/ExtendedXmlSerializerTests.cs b/test/ExtendedXmlSerializer.Tests/ExtensionModel/Xml/ExtendedXmlSerializerTests.cs index 1febc8c60..6d4276ffa 100644 --- a/test/ExtendedXmlSerializer.Tests/ExtensionModel/Xml/ExtendedXmlSerializerTests.cs +++ b/test/ExtendedXmlSerializer.Tests/ExtensionModel/Xml/ExtendedXmlSerializerTests.cs @@ -33,7 +33,7 @@ public void Primitive() [Fact] public void SimpleInstance() { - var instance = new SimpleNestedClass {PropertyName = HelloWorld}; + var instance = new SimpleNestedClass { PropertyName = HelloWorld }; var data = _serializer.Serialize(instance); Assert.Equal( @"Hello World!", @@ -46,21 +46,23 @@ public void SimpleInstance() [Fact] public void ArrayWithBasicNullElement() { - var items = new[] {new Subject(), null, new Subject()}; + var items = new[] { new Subject(), null, new Subject() }; var support = new SerializationSupport(_serializer); support.Cycle(items) - .Should().BeEquivalentTo(items); + .Should() + .BeEquivalentTo(items); } [Fact] public void ListWithBasicNullElement() { - var items = new[] {new Subject(), null, new Subject()}.ToList(); + var items = new[] { new Subject(), null, new Subject() }.ToList(); var support = new SerializationSupport(_serializer); support.Cycle(items) - .Should().BeEquivalentTo(items); + .Should() + .BeEquivalentTo(items); } public sealed class Subject @@ -72,23 +74,27 @@ public sealed class Subject public void ArrayWithNullElement() { var items = new[] - {new SubjectWithProperty {Message = "Hello"}, null, new SubjectWithProperty {Message = "World"}}; + { new SubjectWithProperty { Message = "Hello" }, null, new SubjectWithProperty { Message = "World" } }; var support = new SerializationSupport(_serializer); support.Cycle(items) - .Should().BeEquivalentTo(items); + .Should() + .BeEquivalentTo(items); } [Fact] public void ListWithNullElement() { var items = new[] - {new SubjectWithProperty {Message = "Hello"}, null, new SubjectWithProperty {Message = "World"}} + { + new SubjectWithProperty { Message = "Hello" }, null, new SubjectWithProperty { Message = "World" } + } .ToList(); var support = new SerializationSupport(_serializer); support.Cycle(items) - .Should().BeEquivalentTo(items); + .Should() + .BeEquivalentTo(items); } public sealed class SubjectWithProperty @@ -113,7 +119,7 @@ public void ComplexInstance() [Fact] public void BasicArray() { - var expected = new[] {1, 2, 3, 4, 5}; + var expected = new[] { 1, 2, 3, 4, 5 }; var data = _serializer.Serialize(expected); var actual = _serializer.Deserialize(data); Assert.Equal(expected, actual); @@ -122,7 +128,7 @@ public void BasicArray() [Fact] public void BasicList() { - var expected = new List {"Hello", "World", "Hope", "This", "Works!"}; + var expected = new List { "Hello", "World", "Hope", "This", "Works!" }; var data = _serializer.Serialize(expected); var actual = _serializer.Deserialize>(data); actual.Capacity.Should() @@ -133,7 +139,7 @@ public void BasicList() [Fact] public void BasicHashSet() { - var expected = new HashSet {"Hello", "World", "Hope", "This", "Works!"}; + var expected = new HashSet { "Hello", "World", "Hope", "This", "Works!" }; var data = _serializer.Serialize(expected); var actual = _serializer.Deserialize>(data); Assert.True(actual.SetEquals(expected)); @@ -144,9 +150,9 @@ public void Dictionary() { var expected = new Dictionary { - {1, "First"}, - {2, "Second"}, - {3, "Other"} + { 1, "First" }, + { 2, "Second" }, + { 3, "Other" } }; var data = _serializer.Serialize(expected); var actual = _serializer.Deserialize>(data); @@ -179,7 +185,7 @@ public void GenericProperty() var obj = new TestClassGenericThree(); obj.Init("StringValue", 1, 123.1m); - var expected = new TestClassPropGeneric() {PropGenric = obj}; + var expected = new TestClassPropGeneric() { PropGenric = obj }; var data = _serializer.Serialize(expected); var actual = _serializer.Deserialize(data); @@ -193,9 +199,10 @@ public void GenericProperty() [Fact] public void GenericEnumeration() { - var expected = new GenericSubject {PropertyName = TypeCode.SByte}; + var expected = new GenericSubject { PropertyName = TypeCode.SByte }; _serializer.Cycle(expected) - .Should().BeEquivalentTo(expected); + .Should() + .BeEquivalentTo(expected); } [Fact] @@ -203,7 +210,7 @@ public void DifferingProperty() { const string message = "Hello World! This is a value set in a property with a variable type."; var expected = new ClassWithDifferingPropertyType - {Interface = new Implementation {PropertyName = message}}; + { Interface = new Implementation { PropertyName = message } }; var data = _serializer.Serialize(expected); var actual = _serializer.Deserialize(data); var implementation = Assert.IsType(actual.Interface); @@ -213,7 +220,7 @@ public void DifferingProperty() [Fact] public void Nullable() { - var expected = new NullableSubject {Number = 6776}; + var expected = new NullableSubject { Number = 6776 }; var actual = new SerializationSupport().Assert(expected, @"6776"); @@ -223,17 +230,37 @@ public void Nullable() [Fact] public void NullNullable() { - var expected = new NullableSubject {Number = null}; + var expected = new NullableSubject { Number = null }; var actual = new SerializationSupport().Assert(expected, @""); Assert.Equal(expected.Number, actual.Number); } + [Fact] + public void NullableStructure() + { + var expected = new NullableStructureSubject { Member = new Structure { Message = "Hello World!" } }; + var actual = + new SerializationSupport().Assert(expected, + @"Hello World!"); + Assert.Equal(expected.Member.Value.Message, actual.Member.Value.Message); + } + + [Fact] + public void NullNullableStructure() + { + var expected = new NullableStructureSubject { Member = null }; + var actual = + new SerializationSupport().Assert(expected, + @""); + actual.Member.Should().BeNull(); + } + [Fact] public void Guid() { - var expected = new GuidProperty {Guid = new Guid("7db85a35-1f66-4e5c-9c4a-33a937a9258b")}; + var expected = new GuidProperty { Guid = new Guid("7db85a35-1f66-4e5c-9c4a-33a937a9258b") }; var actual = new SerializationSupport().Assert(expected, @"7db85a35-1f66-4e5c-9c4a-33a937a9258b"); @@ -245,9 +272,9 @@ public void PropertyInterfaceOfList() { var expected = new ClassWithPropertyInterfaceOfList { - List = new List {"Item1"}, - Set = new HashSet {"Item2"}, - Dictionary = new Dictionary {{"Key", "Value"}} + List = new List { "Item1" }, + Set = new HashSet { "Item2" }, + Dictionary = new Dictionary { { "Key", "Value" } } }; const string ns = @@ -266,7 +293,7 @@ public void PropertyInterfaceOfList() [Fact] public void CustomCollection() { - var expected = new TestClassCollection {TestClassPrimitiveTypes.Create()}; + var expected = new TestClassCollection { TestClassPrimitiveTypes.Create() }; var actual = new SerializationSupport().Assert(expected, @"TestString-122343.346-79228162514264337593543950335792281625142643375935439503357.4432NaNINF-INF-3.40282347E+383.40282347E+383.4234NaNINF-INF-1.7976931348623157E+3081.7976931348623157E+308EnumValue123423414223453525342323442014-01-23T00:00:002333g"); @@ -279,7 +306,8 @@ public void DifferentTypeOfProperty() var expected = new TestClassPropertyType(); expected.Init(); _serializer.Cycle(expected) - .Should().BeEquivalentTo(expected); + .Should() + .BeEquivalentTo(expected); } [Fact] @@ -288,7 +316,8 @@ public void ClassWithHashSet() var expected = new TestClassWithHashSet(); expected.Init(); _serializer.Cycle(expected) - .Should().BeEquivalentTo(expected); + .Should() + .BeEquivalentTo(expected); } [Fact] @@ -297,15 +326,17 @@ public void ClassTimeSpan() var expected = new TestClassTimeSpan(); expected.Init(); _serializer.Cycle(expected) - .Should().BeEquivalentTo(expected); + .Should() + .BeEquivalentTo(expected); } [Fact] public void ClassWithArray() { - var expected = new TestClassWithArray() {ArrayOfInt = new int[] {1, 2, 3}}; + var expected = new TestClassWithArray() { ArrayOfInt = new int[] { 1, 2, 3 } }; _serializer.Cycle(expected) - .Should().BeEquivalentTo(expected); + .Should() + .BeEquivalentTo(expected); } [Fact] @@ -313,12 +344,13 @@ public void ClassWithObjectProperty() { var expected = new List { - new TestClassWithObjectProperty {TestProperty = 1234}, - new TestClassWithObjectProperty {TestProperty = "Abc"}, - new TestClassWithObjectProperty {TestProperty = new Guid(1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 2)} + new TestClassWithObjectProperty { TestProperty = 1234 }, + new TestClassWithObjectProperty { TestProperty = "Abc" }, + new TestClassWithObjectProperty { TestProperty = new Guid(1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 2) } }; _serializer.Cycle(expected) - .Should().BeEquivalentTo(expected); + .Should() + .BeEquivalentTo(expected); } #if !CORE @@ -357,6 +389,16 @@ class NullableSubject public int? Number { get; set; } } + class NullableStructureSubject + { + public Structure? Member { get; set; } + } + + struct Structure + { + public string Message { get; set; } + } + class SimpleNestedClass { public string PropertyName { get; set; } From 431c00ec030e465fbef5db7c63c2127615a4e7da Mon Sep 17 00:00:00 2001 From: Mike-E Date: Mon, 23 Nov 2020 02:56:45 -0500 Subject: [PATCH 2/2] Added test with custom serializers. --- .../Issue477Tests_Extended.cs | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 test/ExtendedXmlSerializer.Tests.ReportedIssues/Issue477Tests_Extended.cs diff --git a/test/ExtendedXmlSerializer.Tests.ReportedIssues/Issue477Tests_Extended.cs b/test/ExtendedXmlSerializer.Tests.ReportedIssues/Issue477Tests_Extended.cs new file mode 100644 index 000000000..3048e3919 --- /dev/null +++ b/test/ExtendedXmlSerializer.Tests.ReportedIssues/Issue477Tests_Extended.cs @@ -0,0 +1,41 @@ +using ExtendedXmlSerializer.Configuration; +using ExtendedXmlSerializer.Tests.ReportedIssues.Support; +using FluentAssertions; +using System; +using Xunit; + +namespace ExtendedXmlSerializer.Tests.ReportedIssues +{ + public sealed class Issue477Tests_Extended + { + [Fact] + public void Verify() + { + var serializer = new ConfigurationContainer().Type() + .Register() + .Serializer() + .ByCalling((writer, date) => writer.Content(date.ToString("yyyy-MM-dd")), + null) + .Type() + .Register() + .Serializer() + .ByCalling((writer, date) => writer.Content(date?.ToString("yyyy-MM-dd") ?? string.Empty), null) + .Create() + .ForTesting(); + + var instance = new Test(); + var content = serializer.Serialize(instance); + + content.Should().Be(@"2020-11-232020-11-23"); + + + } + + class Test + { + public DateTime Date1 { get; set; } = DateTime.Now; // Formatted OK + public DateTime? Date2 { get; set; } = null; // Is not emitted = OK + public DateTime? Date3 { get; set; } = DateTime.Now; // Completety ignores configured formatting + } + } +} \ No newline at end of file