2

Why does XmlSerializer populate my object property with an XmlNode array when deserializing an empty typed element using XmlNodeReader instead of an empty string like it does when using StringReader (or XmlTextReader)?

The second assertion in the following code sample fails:

var doc = new XmlDocument();
doc.Load(new StringReader(@"
    <Test xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance""
          xmlns:xsd=""http://www.w3.org/2001/XMLSchema"">
      <Value xsi:type=""xsd:string"" />
    </Test>"));
var ser = new XmlSerializer(typeof (Test));

var reader1 = new StringReader(doc.InnerXml);
var obj1 = (Test) ser.Deserialize(reader1);
Debug.Assert(obj1.Value is string);

var reader2 = new XmlNodeReader(doc.FirstChild);
var obj2 = (Test) ser.Deserialize(reader2);
Debug.Assert(obj2.Value is string);

public class Test
{
    public object Value { get; set; }
}

I'm guessing it has something to do with the null internal NamespaceManager property but I'm not sure how to work around this mysterious limitation. How can I reliably deserialize a subset of my parsed XML document without converting it back into a string and re-parsing?

Nathan Baulch
  • 20,233
  • 5
  • 52
  • 56
  • It looks like you are passing a partial document to the XmlSerializer in the second instance, and the entire document in the first instance, how do you expect it to reconstruct the type from a partial document? – Ron Beyer May 07 '15 at 13:30
  • By the way, if you debug and see what type obj2 is, its of class "Test" but the serializer has deserialized your obj2.Value object to an XmlNode, not a string. Its an interesting side-effect of not passing in the entire document to the serializer. – Ron Beyer May 07 '15 at 13:43
  • 1
    The FirstChild of an XmlDocument is the whole document (doc.FirstChild == doc.DocumentElement). So both readers have the same information, just in different forms (XML string vs parsed XmlNode graph). – Nathan Baulch May 07 '15 at 14:42
  • Try this: `Console.WriteLine(doc.FirstChild.ParentNode == null)`, prints false. The `FirstChild` has a parent node, which is the document. – Ron Beyer May 07 '15 at 14:45
  • If you change the Value property type to string and change the Value element in the XML to `Hello` then both readers work just fine. Deserializing from an XML fragment (partial document) is exactly what XmlNodeReader is for. It just doesn't appear to recognize the xsi:type attribute for some reason. – Nathan Baulch May 07 '15 at 14:46
  • It works if you change `Value` to a string as well in the `Test` class, I guess it just can't cast. – Ron Beyer May 07 '15 at 14:50
  • Maybe a bug. If I use Linq to XML and do `var xdoc = XDocument.Parse(xml); var reader3 = xdoc.Root.CreateReader(); var obj3 = (Test)ser.Deserialize(reader3);` I get a string. Can you switch to the new API? – dbc May 07 '15 at 22:43

1 Answers1

2

It looks like this is a very old XmlNodeReader bug that Microsoft have no intention of fixing. (Archived Microsoft Connect link here). I found a workaround on Lev Gimelfarb's blog here that adds namespaces to the reader's NameTable as prefixes are looked up.

public class ProperXmlNodeReader : XmlNodeReader
{
    public ProperXmlNodeReader(XmlNode node) : base(node)
    {
    }

    public override string LookupNamespace(string prefix)
    {
        return NameTable.Add(base.LookupNamespace(prefix));
    }
}
dbc
  • 104,963
  • 20
  • 228
  • 340
Nathan Baulch
  • 20,233
  • 5
  • 52
  • 56