ExtendedXmlSerializer / home

A configurable and eXtensible Xml serializer for .NET.
https://extendedxmlserializer.github.io/
MIT License
334 stars 47 forks source link

(De)serialization of objects with property-name same as object-name stopped working correctly since V3.2 #598

Closed Heslacher closed 1 year ago

Heslacher commented 1 year ago

Given the classes

public class Person
{
    public string LastName { get; set; } = string.Empty;
    public string FirstName { get; set; } = string.Empty;
    public string Salutation { get; set; } = string.Empty;
}
public class Item
{
    public string Description { get; set; } = string.Empty;
    public string Person { get; set; } = string.Empty;
    public byte[] Content { get; set; }
}

the code

var item = new Item();
item.Person = "Testperson";
item.Description = "Testing ExtendedXmlSerializer";
item.Content = new byte[] { 12, 13, 14, 15, 16 };

string content = serializer.Serialize(new XmlWriterSettings { Indent = true }, item);

var deserializedItem = serializer.Deserialize<Item>(new XmlReaderSettings(), content);

results in deserializedItem.Person being string.Empty and deserializedItem.Content being null

The resulting xml after serialization looks like

<?xml version="1.0" encoding="utf-8"?>
<Item xmlns="clr-namespace:TestingRetryItem;assembly=TestingRetryItem">
  <Description>Testing ExtendedXmlSerializer</Description>
  <Person xmlns:exs="https://extendedxmlserializer.github.io/v2" exs:member="">Testperson</Person>
  <Content>DA0ODxA=</Content>
</Item>

The problem here seems to be exs:member="" because after manually deleting this from the resulting xml the deserialization works. There is no exception thrown neither at serialization nor deserialization. The order of the properties is relevant as well, because e.g having the Item class look like

public class Item
{
    public string Description { get; set; } = string.Empty;
    public byte[] Content { get; set; }
    public string Person { get; set; } = string.Empty;
}

the Content property isn't null anymore. It just seems that after accessing a property with a name equal to a class-name the deserialization stops without throwing an exception.

Mike-E-angelo commented 1 year ago

Hi @Heslacher thank you for writing in and reporting this. Unfortunately I am having a bit of trouble here understanding the exact problem. The following passes for me when I attempt to run it in 3.7.10:

namespace ExtendedXmlSerializer.Tests.ReportedIssues
{
    public sealed class Issue598Tests
    {
        [Fact]
        public void Verify()
        {
            var item = new Item
            {
                Person      = "Testperson",
                Description = "Testing ExtendedXmlSerializer",
                Content     = new byte[] { 12, 13, 14, 15, 16 }
            };

            var container = new ConfigurationContainer().ForTesting();
            container.Cycle(item).Should().BeEquivalentTo(item);

        }
    }

    public class Person
    {
        public string LastName { get; set; } = string.Empty;
        public string FirstName { get; set; } = string.Empty;
        public string Salutation { get; set; } = string.Empty;
    }
    public class Item
    {
        public string Description { get; set; } = string.Empty;
        public string Person { get; set; } = string.Empty;
        public byte[] Content { get; set; }
    }
}

If you are saying that you serialized something in 3.2 but it no longer does so in 3.7.x I am not sure how much of a help I can be toward resolving this. While I do my best to carry over previous functionality, I am really the only developer on this project so problems may be had. :)

That stated, please let me know what I am missing here and I can continue to see if I can assist you. ๐Ÿ‘

Heslacher commented 1 year ago

Hi @Mike-E-angelo thanks for answering. I tested it again just now with 3.7.10 with the code I provided. After deserializing the item isn't the same. I get an empty string for Person and null for Content of the item object.

Heslacher commented 1 year ago

Hang on, I think I found the problem...

Mike-E-angelo commented 1 year ago

Hehe indeed @Heslacher I noticed with your code the configuration of the container is missing so maybe it might have something to do with this? ๐Ÿค” With a default container everything appears to work with the code you have provided. ๐Ÿ‘

Heslacher commented 1 year ago

Ok, the problem lies in the creation of the IExtendedXmlSerializer object. Creating it with EnableReferences() will lead to the mentioned problem with deserialization. Omitting this call will lead to a correct result.

Heslacher commented 1 year ago

Hehe indeed @Heslacher I noticed with your code the configuration of the container is missing so maybe it might have something to do with this? ๐Ÿค” With a default container everything appears to work with the code you have provided. ๐Ÿ‘

I just saw this, my bad. Thanks for the help. ;-)

Heslacher commented 1 year ago

Just for completeness this is how I created the IExtendedXmlSerializer object having the mentioned issue:

        IExtendedXmlSerializer serializer = new ConfigurationContainer()
                     .EnableAllConstructors()
                     .EnableReferences()
                     .UseOptimizedNamespaces()
                     .Create();  
Mike-E-angelo commented 1 year ago

Ohp... yep, I am able to reproduce this now. I will poke around to see what's happening here as this seems it should work. I am very displeased w/ myself for using the exs:member ... there are a couple issues with it and I was seeing if there is some method I added to help with this scenario.

I concede this is not ideal at all, but you may have to consider using a different name for the member name than the type name ๐Ÿ˜ž

Mike-E-angelo commented 1 year ago

I do apologize for the disruption here @Heslacher. I have been spending the past hour looking into this and it is complex, especially since it's been a few years now since I have been in this code. The issue is that the XmlReader cursor is on the attribute node and I am not finding an easy way to forward it to the content without breaking existing tests.

However, in your case, since you are using primitive types, we do have an extension called AutoFormatting which moves strings into attributes, and this makes your test pass.

The recommendation here is to use the following configuration:

        IExtendedXmlSerializer serializer = new ConfigurationContainer()
                     .EnableAllConstructors()
                     .EnableReferences()
                     .UseOptimizedNamespaces()
                     .UseAutoFormatting()
                     .Create();

This does change your documents (it should decrease overall size), but you might want to verify things work on your end after the change. Let me know how you would like to proceed here. Unfortunately I do not have as much time as I did in the past to look into such issues, so we'll have to do the best with the time we do have. ๐Ÿ‘

Heslacher commented 1 year ago

You don't need to apologize. I have fixed the issue by using EnableReferences() for serialization and omitting it for deserialization. I have tested it and it works. Thanks again for your help and this wonderful project.

Mike-E-angelo commented 1 year ago

Thank you very much! I appreciate that. I definitely feel beholden to this project and all the developers that depend on it, so when things don't work I take that responsibility seriously.

I like your other solution as well. I didn't even think of that, and hopefully, if someone in the future runs into this problem they have a couple of workarounds they can possibly implement.

Anyways, thank you for your understanding and kind words. You made my day. ๐Ÿ™ Closing for now.