Roman Starkov
Roman Starkov

Reputation: 61502

Use XDocument as the source for XmlSerializer.Deserialize?

I would like to invoke XmlSerializer.Deserialize passing it an XDocument. It can take a Stream, an XmlReader or a TextReader.

Can I generate one of the above from XDocument without actually dumping the XDocument into some intermediate store, such as a MemoryStream?

It seems that what I'm after is an implementation of XmlReader that works with an XDocument. I can't find one though.

Upvotes: 45

Views: 34724

Answers (5)

KarmaEDV
KarmaEDV

Reputation: 1691

I like @Simon_Weaver 's answer the best. Based on that this is my summary:

using System;
using System.Xml.Linq;
using System.Xml.Serialization;

namespace XDocSerialization
{
    [TestClass]
    public class Tests
    {
        [TestMethod]
        public void Tests_SerializeToXDoc()
        {
            var sheep = new Animal
            {
                Name = "Sheep", Legs = 4, Nutrition = Nutrition.Herbivore
            };
            var xdoc = sheep.SerializeToXDoc();
            var ser = "<Animal " +
                      "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " +
                      "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">\r\n  " +
                      "<Name>Sheep</Name>\r\n  <Legs>4</Legs>\r\n  " +
                      "<Nutrition>Herbivore</Nutrition>\r\n</Animal>";

            Assert.AreEqual(xdoc.ToString(), ser);
            Assert.IsInstanceOfType(xdoc, typeof(XDocument));
        }

        [TestMethod]
        public void Tests_DeserializeFromXDoc()
        {
            var Sheep = new Animal
            {
                Name = "Sheep", Legs = 4, Nutrition = Nutrition.Herbivore
            };
            var des = Sheep.SerializeToXDoc().DeserializeFromXDoc<Animal>();

            Assert.AreEqual(des.Name, Sheep.Name);
            Assert.AreEqual(des.Nutrition, Sheep.Nutrition);
            Assert.AreEqual(des.Legs, Sheep.Legs);
            Assert.AreNotSame(des, Sheep);
        }
    }

    public static class ExtensionMethods
    {
        public static T DeserializeFromXDoc<T>(this XDocument source)
        {
            if (source == null || source.Root == null)
                return default(T);

            using (var reader = source.Root.CreateReader())
                return (T)new XmlSerializer(typeof(T)).Deserialize(reader);
        }

        public static XDocument SerializeToXDoc<T>(this T source)
        {
            if (source == null)
                return null;

            var doc = new XDocument();
            using (var writer = doc.CreateWriter())
                new XmlSerializer(typeof(T)).Serialize(writer, source);

            return doc;
        }
    }

    [Serializable]
    public class Animal
    {
        public string Name { get; set; }
        public int Legs { get; set; }
        public Nutrition Nutrition { get; set; }
    }

    public enum Nutrition
    {
        Herbivore,
        Carnivore,
        Omnivore
    }
}

Upvotes: 0

Slauma
Slauma

Reputation: 177163

(Appendix to Steve Guidi's answer)

As far as I can see there is no implementation of XmlReader you can easily use with XDocument without moving the XML content through an intermediate store like a string representation of the XML and that supports all types that for example the System.Xml.XmlNodeReader supports.

The reader returned by XDocument.CreateReader (which is a System.Xml.Linq.XNodeReader, an internal class) is a XmlReader and works for most Xml document but not with documents that have binary data elements because its implementation does not support Base64 or BinHex data:

Base64 and BinHex data are not supported. If you attempt to retrieve these types of data (for example, by calling ReadElementContentAsBase64), the reader will throw NotSupportedException.

For this reader XDocument.CreateReader().CanReadBinaryContent is false in contrast to the System.Xml.XmlNodeReader.

For example this program throws an exception:

public class SomeTest
{
    public byte[] BinaryTest { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        XDocument document = new XDocument(
            new XElement("SomeTest",
                new XElement("BinaryTest", "VGVzdA==")));

        using (var reader = document.CreateReader())
        {
            var serializer = new XmlSerializer(typeof(SomeTest));
            var someTest = serializer.Deserialize(reader) as SomeTest;
            // NotSupportedException here (as inner exception)
        }
    }
}

However, extracting the XML as string and passing it as TextReader into the serializer works:

        using (var reader = new StringReader(document.ToString()))

I would be interested as well if there is another way to deserialize an XDocument that includes binary data without converting it into a string first.

Upvotes: 5

Simon_Weaver
Simon_Weaver

Reputation: 146110

Here's a utility to serialize and deserialize objects to/from XDocument.

XDocument doc = SerializationUtil.Serialize(foo);
Foo foo = SerializationUtil.Deserialize<Foo>(doc);

Here's the class:

public static class SerializationUtil
{
    public static T Deserialize<T>(XDocument doc)
    {
        XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));

        using (var reader = doc.Root.CreateReader())
        {
            return (T)xmlSerializer.Deserialize(reader);
        }
    }

    public static XDocument Serialize<T>(T value)
    {
        XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));

        XDocument doc = new XDocument();
        using (var writer = doc.CreateWriter())
        {
            xmlSerializer.Serialize(writer, value);
        }

        return doc;
    }
}

Upvotes: 20

dotNetkow
dotNetkow

Reputation: 5313

Just thought I should add that after the XmlReader is created, i.e.:

XmlSerializer serializer = new XmlSerializer(typeof(MyObject));
XmlReader reader = xmlDocumentToDeserialize.CreateReader();

then you should call:

reader.MoveToContent();

because otherwise the reader will not "point" to the first node, causing the appearance of an empty reader! Then you can safely call Deserialize:

MyObject myObject = (MyObject)serializer.Deserialize(reader);

Upvotes: 0

Steve Guidi
Steve Guidi

Reputation: 20200

You can use XDocument.CreateReader() to create an XmlReader that reads the contents of the XDocument.

Equivalently, the following will work too.

XmlReader GetReader(XDocument doc)
{
    return doc.Root.CreateReader();
}

Upvotes: 57

Related Questions