views:

493

answers:

5

Hello everyone,

I am using C# + VSTS2008 + .Net 3.0 to do XML serialization. The code works fine. Here below is my code and current serialized XML results.

Now I want to add two additional layers to the output XML file. Here is my expected XML results. Any easy way to do this? I am not sure whether NestingLevel could help to do this. I want to find an easy way which does not change the structure of MyClass and MyObject.

Expected XML serialization result,

<?xml version="1.0"?>
<MyClass xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 

xmlns:xsd="http://www.w3.org/2001/XMLSchema"&gt;
  <MyObjectProperty>
    <AdditionalLayer1>
      <AdditionalLayer2>
        <ObjectName>Foo</ObjectName>
      </AdditionalLayer1>
    </AdditionalLayer2>
  </MyObjectProperty>
</MyClass>

Current XML serialization result,

<?xml version="1.0"?>
<MyClass xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 

xmlns:xsd="http://www.w3.org/2001/XMLSchema"&gt;
  <MyObjectProperty>
    <ObjectName>Foo</ObjectName>
  </MyObjectProperty>
</MyClass>

My current code,

public class MyClass
{
    public MyObject MyObjectProperty;
}
public class MyObject
{
    public string ObjectName;
}

public class Program
{
    static void Main(string[] args)
    {
        XmlSerializer s = new XmlSerializer(typeof(MyClass));
        FileStream fs = new FileStream("foo.xml", FileMode.Create);
        MyClass instance = new MyClass();
        instance.MyObjectProperty = new MyObject();
        instance.MyObjectProperty.ObjectName = "Foo";
        s.Serialize(fs, instance);

        return;
    }
}

thanks in advance, George

+1  A: 

I would suggest serializing out to a MemoryStream instead of a FileStream and then loading the XML into an XmlDocument, then using the methods of that class to insert the extra nodes you want. After that, you can save it out.

Jon Grant
If you were going that route, a StringWriter would be simpler; no need to encode/decode.
Marc Gravell
Why serializing out to a MemoryStream needs encoded/decode? Do you mean get/set UTF-8 bytes from XML string?
George2
+2  A: 

You could do a transformation with XSL after serializing and add the "missing" tags.

Scoregraphic
XSL you mean XSLT?
George2
As the T in XSLT stands for transformation and a transformation transformation does not make much sense, I omitted the T, but yes ;-)
Scoregraphic
I always think XSL is tricky. Anyway, recommend a good book for XSL?
George2
Not really...I learned it by doing and Google, but see http://oreilly.com/catalog/9780596000530/ and related books
Scoregraphic
+3  A: 

I want to find an easy way which does not change the structure of MyClass and MyObject.

Frankly, that isn't going to happen. XmlSerializer (when used simply) follows the structure of the classes / members. So you can't add extra layers without changing the structure.

When used in a bespoke way (IXmlSerializable), the one thing you can say for sure is that it isn't simple... this is not a fun interface to implement.

My advice: introduce a DTO that mimics your desired format, and shim to that before you serialize.

Marc Gravell
Thanks Marc, I have a related question, I find only property/member name is serialized into XML as element name, class name is not serialized as element name, for example for member "public MyObject MyObjectProperty", element is named by "MyObjectProperty", but not by "MyObject". Does that design makes more senses compared of using type name?
George2
If you want control of the names, then use `[XmlElement(...)]`, `[XmlAttribute(...)]`, `[XmlType(...)]` and `[XmlRoot(...)]` - the first two are the most useful (for example, on the offending property).
Marc Gravell
"DTO" you mean?
George2
Sorry Marc, I do not mean I want to change names. My question is about .Net XML serialization design, and you can see when we serialize nested types, member/property types are missing but member/type names are kept. Why it is designed in this way and does not keep type information?
George2
"data transfer object"; a separate object model used primarily for serialization and simple things like that. It doesn't have to have the business logic (leave that for the *actual* objects); all it has to do is look the right shape...
Marc Gravell
Because it is a **contract** serializer, not a **type** serializer. It is important that it *doesn't matter* what the types are. I can provide a completely separate set of classes which will work fine, as long as they agree on the contract (i.e. the xml names etc). Also consider that commonly there will be proxy classes consuming xml as a web-service, and that code might not be .NET; so there is almost no place for type names in xml - just data.
Marc Gravell
Thanks for your great answer Marc, I have a related question, appreciate if you could take a look, http://stackoverflow.com/questions/1227693/xml-array-serialization-issue-in-c
George2
+4  A: 

Why do you need these additional two layers?

Is its a business requirement, you should add these two objects to your object model:

public class MyClass
{
    public AdditionalLayer1 AdditionalLayer1;
}
public class AdditionalLayer1
{
    public AdditionalLayer2 AdditionalLayer2;
}
public class AdditionalLayer2
{
    public MyObject MyObjectProperty;
}
public class MyObject
{
    public string ObjectName;
}

if its purely because of some compatibility requirements you can either do the thing above, or implement IXmlSerializable on MyClass:

public class MyClass : IXmlSerializable
{
    public MyObject MyObjectProperty;

    public void WriteXml (XmlWriter writer)
    {
        //open the two layers
        //serialize MyObject
        //close the two layers
    }

    public void ReadXml (XmlReader reader)
    {
        //read all the layers back, etc
    }

    public XmlSchema GetSchema()
    {
        return(null);
    }

}
Grzenio
I have a related question, I find only property/member name is serialized into XML as element name, class name is not serialized as element name, for example for member "public MyObject MyObjectProperty", element is named by "MyObjectProperty", but not by "MyObject". Does that design makes more senses compared of using type name?
George2
I guess it makes more sense, because you could reuse the same type in two different contexts in a single class and you want to serialize it using different element names.
Grzenio
A: 

I did exactly this only recently and didn't find overriding the IXmlSerializable interface at all difficult or complicated. Serialize/deserialize as normal but for the class that contains the sub-class which you need to wrap in extra tags derive from IXmlSerializable:

class ContainingClass : IXmlSerializable
{
 ...

#region IXmlSerializable Members

public XmlSchema GetSchema()
{
  return null;
}

public void ReadXml(XmlReader reader)
{
  **reader.ReadStartElement("Equipment");**

  XmlSerializer ser = new XmlSerializer(typeof(Host));

  while (reader.NodeType != XmlNodeType.EndElement)
  {
    Host newHost = new Host();
    newHost.Name = (string) ser.Deserialize(reader);
    _hosts.Add(newHost);

    reader.Read();
  }

  // Read the node to its end.
  // Next call of the reader methods will crash if not called.
  **reader.ReadEndElement(); // "Equipment"**
}

public void WriteXml(XmlWriter writer)
{
  XmlSerializer ser = new XmlSerializer(typeof(Host));
  **writer.WriteStartElement("Equipment");**
  foreach (Host host in this._hosts)
  {
    ser.Serialize(writer, host);
  }
  **writer.WriteEndElement();**
}

#endregion

All I do here is to wrap the de/serialization of the lower level objects. Here I'm wrapping an extra tag "Equipment" around all the Hosts in a collection in this class. You could add as many tags as you like here as long as you close each one you start.

NOTE: Bold is showing as ** text ** - just make sure you get rid of the double asterisks :)