1

How to serialize multiple objects into a existing XmlDocument in .Net/C#?

I have a XmlDocument, which already contains data. I have multiple objects. Now I want to serialize them one by one and add them to the XmlDocument (AppendChild).

This is how it should be:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<project>
    <mySettings>...</mySettings>
    <component_1> anydata </component_1>
    ...
    <component_x> anydata </component_x>
</project>

When I use the XmlSerializer I get this definition for each component:

<component_1 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    anydata
</component_1>

So this is what I get, when I serialize into a string and then create a XmlNode from string, which i append to my document:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<project>
    <mySettings>...</mySettings>
    <component_1 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> anydata </component_1>
    ...
    <component_x xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> anydata </component_x>
</project>

I can remove the namespace by doing this:

XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("", "");
StringWriter xout = new StringWriter();
x.Serialize(xout, data, ns);

But then I get the namespaces on any object inside an object array. This object:

public class component_1
{
    object[] arr;
}

will be serialized to:

<component_1>
  <objectArray>
    <anyType xmlns:q1="http://www.w3.org/2001/XMLSchema" d3p1:type="q1:string" xmlns:d3p1="http://www.w3.org/2001/XMLSchema-instance">one</anyType>
    <anyType xmlns:q2="http://www.w3.org/2001/XMLSchema" d3p1:type="q2:string" xmlns:d3p1="http://www.w3.org/2001/XMLSchema-instance">two</anyType>
  </objectArray>
</component_1>

Is it possible to add all needed namespaces, etc. to my document and then serialize objects into XmlNodes and add them to my document, without having the namespaces on each component?

UPDATE: The function test() will serialize two objects and append them to a document. Last line will deserialize the first object.

using System;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
...

public class Component_1
{
    public string Value = "Component_1.Value";
    public object[] objectArray = new object[] { "one", "two" };
}
void test()
{
    object[] components = new object[] { new Component_1(), new Component_1() };

    XmlDocument doc = new XmlDocument();
    XmlNode rootNode = doc.AppendChild(doc.CreateElement("project"));

    foreach (var component in components)
        rootNode.AppendChild(doc.ReadNode(XmlTextReader.Create(new StringReader(serialize(component, true)))));

    Console.WriteLine(doc.OuterXml);
    Console.WriteLine(deserialize<Component_1>(rootNode.ChildNodes[0].OuterXml).Value);
}
string serialize(object obj, bool namespaces)
{
    StringBuilder sb = new StringBuilder();
    XmlWriter writer = XmlWriter.Create(sb, new XmlWriterSettings() { OmitXmlDeclaration = true });
    (new XmlSerializer(obj.GetType())).Serialize(writer, obj, namespaces ? null : new XmlSerializerNamespaces(new XmlQualifiedName[] { new XmlQualifiedName("", "") }));
    return sb.ToString();
}
T deserialize<T>(string xmlString)
{
    return (T)(new XmlSerializer(typeof(T))).Deserialize(new StringReader(xmlString));
}

Maybe it is possible to add namespaces to the document (rootNode) and when creating a new XmlNode from string with the function XmlDocument.ReadNode to resolve the namespaces in string by the namespaces from XmlDocument. But I don´t know how.

UPDATE 2:
Thanks Alex Filipovici, the serialization output is exact what I wanted.

void test2()
{
    object[] components = new object[] { new Component_1(), new Component_1() };

    var doc = new XmlDocument();

    var project = doc.AppendChild(doc.CreateElement("project"));

    doc.DocumentElement.SetAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
    doc.DocumentElement.SetAttribute("xmlns:xsd", "http://www.w3.org/2001/XMLSchema");

    var nav = project.CreateNavigator();

    var emptyNamepsaces = new XmlSerializerNamespaces(new[] { XmlQualifiedName.Empty });

    foreach (var component in components)
    {
        using (var writer = nav.AppendChild())
        {
            var serializer = new XmlSerializer(component.GetType());
            writer.WriteWhitespace("");
            serializer.Serialize(writer, component
                , emptyNamepsaces
                );
            writer.Close();
        }
    }

    foreach (XmlNode node in doc.GetElementsByTagName("anyType"))
    {
        string attributeType = "";
        foreach (XmlAttribute xmlAttribute in node.Attributes)
        {
            if (xmlAttribute.LocalName == "type")
            {
                attributeType = xmlAttribute.Value.Split(':')[1];
            }
        }
        node.Attributes.RemoveAll();
        node.CreateNavigator().CreateAttribute("", "type", "", attributeType);
    }
    doc.Save("output.xml");

    Component_1 c = deserialize<Component_1>(project.ChildNodes[0].OuterXml);

    Console.WriteLine(c.objectArray[0].GetType()); // -> System.Xml.XmlNode[] !
}

output:

<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Component_1>
    <Value>Component_1.Value</Value>
    <objectArray>
      <anyType type="string">one</anyType>
      <anyType type="string">two</anyType>
    </objectArray>
  </Component_1>
  <Component_1>
    <Value>Component_1.Value</Value>
    <objectArray>
      <anyType type="string">one</anyType>
      <anyType type="string">two</anyType>
    </objectArray>
  </Component_1>
</project>

But now the deserialization with the "T desirialize(string xmlString)" function from above fails. The object array contains XmlNodes.

Is it possible to tell XmlSerializer to use the namespaces from the project node, or do I have to insert them again?

svick
  • 236,525
  • 50
  • 385
  • 514
Alex
  • 71
  • 1
  • 6
  • Why do you need to remove the namespaces? – John Saunders Feb 07 '13 at 20:57
  • See [Serialize object to XmlDocument](http://stackoverflow.com/questions/781442/serialize-object-to-xmldocument) for some ideas. Maybe if the XmlDocument already has elements with namespaces, they won't need to be redeclared. I'm concerned that you are causing yourself problems by serializing to string, creating an XmlNode, and then appending that. – John Saunders Feb 07 '13 at 20:58
  • @John Saunders:Why? - If you have many components, it will unnecessarily increase the size of the xml file, because each component uses the same namespace. – Alex Feb 08 '13 at 10:03
  • How much does the size matter? – John Saunders Feb 08 '13 at 14:44
  • Maybe, I don´t know at the moment. – Alex Feb 09 '13 at 20:24

2 Answers2

4

This will serialize objects and append them to a XmlDocument. While de-/serializing the code will resolve the namespaces. @Alex: Thanks for the example with XPathNavigator.

void test2()
{
    XmlDocument doc = new XmlDocument();
    XmlNode root = doc.AppendChild(doc.CreateElement("root"));

    doc.DocumentElement.SetAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
    doc.DocumentElement.SetAttribute("xmlns:xsd", "http://www.w3.org/2001/XMLSchema");

    serializeAppend(root, new object[] { 1, "two", 3.0 });  // serialize object and append it to XmlNode
    var obj = deserialize<object[]>(root.ChildNodes[0]);    // deserialize XmlNode to object
}
T deserialize<T>(XmlNode node)
{
    XPathNavigator nav = node.CreateNavigator();
    using (var reader = nav.ReadSubtree())
    {
        var serializer = new XmlSerializer(typeof(T));
        return (T)serializer.Deserialize(reader);
    }
}
void serializeAppend(XmlNode parentNode, object obj)
{
    XPathNavigator nav = parentNode.CreateNavigator();
    using (var writer = nav.AppendChild())
    {
        var serializer = new XmlSerializer(obj.GetType());
        writer.WriteWhitespace("");
        serializer.Serialize(writer, obj);
        writer.Close();
    }
}
Alex
  • 71
  • 1
  • 6
1

The code below will fulfill the requirement in the OP, to have a clean XML. It will remove all tributes from all elements, but it will add a type attribute to the anyType elements, so the original type could still be distinguish for each element.

static void Main(string[] args)
{
    object[] components = new object[] { new Component_1(), new Component_1() };

    var doc = new XmlDocument();
    doc.Load("source.xml");
    var project = doc.GetElementsByTagName("project")[0];

    var nav = project.CreateNavigator();

    var emptyNamepsaces = new XmlSerializerNamespaces(new[] { 
        XmlQualifiedName.Empty
    });

    foreach (var component in components)
    {
        using (var writer = nav.AppendChild())
        {
            var serializer = new XmlSerializer(component.GetType());
            writer.WriteWhitespace("");
            serializer.Serialize(writer, component
                , emptyNamepsaces
                );
            writer.Close();
        }
    }

    foreach (XmlNode node in doc.GetElementsByTagName("anyType"))
    {
        string attributeType = "";
        foreach (XmlAttribute xmlAttribute in node.Attributes)
        {
            if (xmlAttribute.LocalName == "type")
            { 
            attributeType=xmlAttribute.Value.Split(':')[1];
            }
        }
        node.Attributes.RemoveAll();
        node.CreateNavigator().CreateAttribute("","type","",attributeType);
    }
    doc.Save("output.xml");

}

If you want to deserialize the XML, you'll have to create a dictionary:

static Dictionary<string, Type> _typeCache;

add the expected XML types mapped to corresponding Type values to it:

_typeCache = new Dictionary<string, Type>();
_typeCache.Add("string", typeof(System.String));
_typeCache.Add("int", typeof(System.Int32));
_typeCache.Add("dateTime", typeof(System.DateTime));

and replace each XmlNode in the array by converting it to it's expected type accordingly:

Component_1 c = Deserialize<Component_1>(project.ChildNodes[0].OuterXml);

for (int i = 0; i < c.objectArray.Length; i++)
{
    var type = _typeCache[(((System.Xml.XmlNode[])(c.objectArray[i]))[0]).Value];
    var item = Convert.ChangeType((((System.Xml.XmlNode[])(c.objectArray[i]))[1]).Value, type);
    c.objectArray[i] = item;
}

Console.WriteLine(c.objectArray[0].GetType()); // -> System.String
Alex Filipovici
  • 31,789
  • 6
  • 54
  • 78