ExtendedXmlSerializer / home

A configurable and eXtensible Xml serializer for .NET.

Home Page:https://extendedxmlserializer.github.io/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Question: Deserializing with unknown content

josdemmers opened this issue · comments

After reading the wiki at:
https://github.com/ExtendedXmlSerializer/home/wiki/Example-Scenarios#unknown-content

It does not look like it is working correctly for me.

I was testing the WithUnknownContent() option because an xml document changed and some fields are now deprecated.
Hopefully this can easily solve the problem without using Xslt.

But before I got there I noticed something strange with WithUnknownContent() when trying it out on an xml file without any unknown content.

var configurationContainer = new ConfigurationContainer()
  .EnableImplicitTyping(typeof(InputModel))
  .Create();
var inputModel = configurationContainer.Deserialize<InputModel>(new XmlReaderSettings { IgnoreWhitespace = false }, inputXml);

var configurationContainer2 = new ConfigurationContainer()
  .EnableImplicitTyping(typeof(InputModel))
  .WithUnknownContent()
  .Continue()
  .Create();
var inputModel2 = configurationContainer2.Deserialize<InputModel>(new XmlReaderSettings { IgnoreWhitespace = false }, inputXml);

I tried the above configurationContainers to deserialize an xml file without unknown content but noticed the deserialization no longer works when I add WithUnknownContent().
For var inputModel the deserialization works.
For var inputModel2 the deserialization does not work.

Any idea why the deserialization stops working when I add WithUnknownContent?

Let me know if you need the InputModel class or an example XML file.

Hi @josdemmers thank you for writing in with this. Unfortunately, I will need a little more information to further diagnose/troubleshoot. A simple model with the document content that reproduces what you are experiencing will suffice.

Also, it is a little unclear here with the use of XmlReaderSettings but are you saying that the use of this also contributes to the issue that you are experiencing?

Ok I simplified the model as much as possible so it would still cause the same issue.

XML

<InputModelExample>
	<Configuration xmlns:exs="https://extendedxmlserializer.github.io/v2" exs:member="">
		<Language>LAD</Language>
		<Section>Section1</Section>
		<Plc>FC-6011-SB1-CPU</Plc>
	</Configuration>
	<ModuleStates>
		<ModuleState>
			<Name>BUC</Name>
		</ModuleState>
	</ModuleStates>
</InputModelExample>

Relevant classes

    public class InputModelExample
    {
        public Configuration Configuration { get; set; }
        public List<ModuleState> ModuleStates { get; set; } = new List<ModuleState>();
        //public List<Module> Modules { get; set; } = new List<Module>();
        //public List<Variable> Variables { get; set; } = new List<Variable>();
        //public DBs DataBlocks { get; set; }
    }

  public class Configuration
  {
    public string Language { get; set; }
    public string Section { get; set; }
    public string Plc { get; set; }
  }

  public class ModuleState
  {
    public string Name { get; set; }
  }

Relevant code

var configurationContainer = new ConfigurationContainer()
  .EnableImplicitTyping(typeof(InputModelExample))
  .Create();
var inputModel = configurationContainer.Deserialize<InputModelExample>(new XmlReaderSettings { IgnoreWhitespace = false }, inputXml);

var configurationContainer2 = new ConfigurationContainer()
  .EnableImplicitTyping(typeof(InputModelExample))
  .WithUnknownContent()
  .Continue()
  .Create();
var inputModel2 = configurationContainer2.Deserialize<InputModelExample>(new XmlReaderSettings { IgnoreWhitespace = false }, inputXml);

Regarding XmlReaderSettings that is only relevant because it is part of my Deserialize call.

Issue
Using configurationContainer I get a new InputModelExample that contains the Configuration and ModuleStates from the above mentioned XML.
Using configurationContainer2 i get a new InputModelExample that contains the Configuration from the above mentioned XML. However the ModuleStates list is then empty.

Using Throw instead of Continue I can get the following exception:

{"Unknown/invalid member encountered: 'ModuleState'. Line 8, position 4."}

Which is strange because the ModuleState class exists and when using configurationContainer instead of configurationContainer2 the Deserializer has no problem creating an InputModelExample.

OK great @josdemmers thank you very much for the additional information and the quick reproduction. It is very helpful.

I believe I see what you are encountering. If I take the following:

var instance = new InputModelExample
			{
				Configuration = new Configuration(),
				ModuleStates  = new List<ModuleState>() { new ModuleState { Name = "BUC" } }
			};

And serialize it using configurationContainer , I get the following (whitespace/formatting added for readability):

<?xml version="1.0" encoding="utf-8"?>
<Issue571Tests-InputModelExample>
	<Configuration />
	<ModuleStates>
		<Capacity>4</Capacity>
		<Issue571Tests-ModuleState xmlns="clr-namespace:ExtendedXmlSerializer.Tests.ReportedIssues;assembly=ExtendedXmlSerializer.Tests.ReportedIssues">
			<Name>BUC</Name>
		</Issue571Tests-ModuleState>
	</ModuleStates>
</Issue571Tests-InputModelExample>

You can see that ModuleState still requires a namespace. If it isn't provided, then it is resolved as unknown content. See if the following configuration treats you better:

var configurationContainer = new ConfigurationContainer()
  .EnableImplicitTyping(typeof(InputModelExample), typeof(ModuleState))
  .Create();

I have added a quick test confirming this using the model you provided here, for your review:

[Fact]
public void Verify()
{
const string content = @"<?xml version=""1.0"" encoding=""utf-8""?>
<Issue571Tests-InputModelExample>
<Configuration />
<ModuleStates>
<Capacity>4</Capacity>
<Issue571Tests-ModuleState>
<Name>BUC</Name>
</Issue571Tests-ModuleState>
</ModuleStates>
</Issue571Tests-InputModelExample>";
var subject = new ConfigurationContainer()
.EnableImplicitTyping(typeof(InputModelExample), typeof(ModuleState))
.WithUnknownContent()
.Throw()
.Create()
.ForTesting();
subject.Invoking(support => support.Deserialize<InputModelExample>(content)).Should().NotThrow();
}
public class InputModelExample
{
public Configuration Configuration { get; set; }
public List<ModuleState> ModuleStates { get; set; } = new List<ModuleState>();
}
public class Configuration
{
public string Language { get; set; }
public string Section { get; set; }
public string Plc { get; set; }
}
public class ModuleState
{
public string Name { get; set; }
}

Please do let me know if you have any further questions/issues and I will do my best to assist you. 👍

When expanding EnableImplicitTyping with .EnableImplicitTyping(typeof(InputModelExample), typeof(ModuleState))
Then the configurationContainer that uses WithUnknownContent works as well.
Thank you, now can I do some tests with xml files with deprecated properties.

Regarding the serializer, when I serialize it the xml does not contain any namespaces but that is probably because I used EnableImplicitTypingFromNamespacePublic.

Just out of interest, one more question regarding the configurationContainers in my previous example.
Any reason why the configurationContainer (without the WithUnknownContent configuration) does not have any problems with the missing namespace? But configurationContainer2 (with the WithUnknownContent configuration) does not like the missing namespace?

Just tested WithUnknownContent with some deprecated properties in my xml files, works perfect.
Deprecated properties are ignored and next elements are added without any problem!

So very happy to hear it works as expected for you @josdemmers!

Any reason why the configurationContainer (without the WithUnknownContent configuration) does not have any problems with the missing namespace? But configurationContainer2 (with the WithUnknownContent configuration) does not like the missing namespace?

Indeed a very good reason: I wrote broken software. 😁 The default behavior starting in v2 and into v3 (unfortunately we did not catch it in time) is to essentially fail silently. The deserialization "works" but you'll notice the list is empty, or even populated with one element that doesn't have its properties assigned. I added a new test to verify this with your model:

[Fact]
public void VerifyDefault()
{
const string content = @"<?xml version=""1.0"" encoding=""utf-8""?>
<Issue571Tests-InputModelExample>
<Issue571Tests-Configuration xmlns:exs=""https://extendedxmlserializer.github.io/v2"" exs:member="""">
<Language>LAD</Language>
<Section>Section1</Section>
<Plc>FC-6011-SB1-CPU</Plc>
</Issue571Tests-Configuration>
<ModuleStates>
<Issue571Tests-ModuleState>
<Name>BUC</Name>
</Issue571Tests-ModuleState>
</ModuleStates>
</Issue571Tests-InputModelExample>";
var subject = new ConfigurationContainer()
.EnableImplicitTyping(typeof(InputModelExample))
.Create()
.ForTesting();
subject.Deserialize<InputModelExample>(content).ModuleStates.Should().BeEmpty();
}
public class InputModelExample
{
public Configuration Configuration { get; set; }
public List<ModuleState> ModuleStates { get; set; } = new List<ModuleState>();
}
public class Configuration
{
public string Language { get; set; }
public string Section { get; set; }
public string Plc { get; set; }
}
public class ModuleState
{
public string Name { get; set; }
}

This was actually the impetus for the WithUnknownContent APIs. If we ever do a v4 (which is doubtful as we are basically in maintenance mode now but who knows), the default behavior will be made to throw on exception and essentially obsolete WithUnknownContent().Throw().

Anyways, hope that answers your question. If you do have any further questions/issues please let me know we'll see if we can help you out.

Thanks again, that answers my question.