.NET Framework - Possible to change XML root element name thru XmlSerializer?

Asked By csharper on 11-Nov-11 09:26 PM
If I have this:

public class Book
{
public string Title {get; set;}
public string Author {get; set; }
}

When I XmlSerialize it, the root element of the XML will be Book.

I want it to be Book sometimes and at some other time I want it to be Libros, and still some other time to be TextBook.

Is this achievable thru this single Book class? Is there an XmlSerializer constructor that takes a string parameter as the root element name?

Must I modify the Book class's XmlRootAttribute value thru reflection if at all feasible? Any suggestions? Thanks.




Peter Duniho replied to csharper on 11-Nov-11 10:51 PM
As far as I know, custom attributes for a type are read-only.  I do not
think even through reflection you can add an XmlRootAttribute.  (Though,
you raise an interesting point; I suppose there might be a place in the
serialization API to allow the caller to provide a dictionary of types
to attributes and other run-time settings to customize serialization).

I assume from your question that you do not have the option of just
adding the attribute at compile-time yourself.  That is, this type is in
an assembly you do not own and do not compile.

Note that changing the name in the XML would prevent the type from being
able to be deserialized by other code that uses the same type.  Often
this would not be an issue, but if you are dealing with other assemblies,
using serialization to transfer object data, this is not something you would
want to do.

Of course, one possible option would be to subclass the type and
serialize/deserialize that.  Just make that subclass's name be whatever
you want the XML to read.

Another option is to post-/pre-process the XML.  That is, serialize the
object, then modify the XML directly.  And before deserializing, modify
the XML again so that it matches what is needed.

Finally, of course, yet another option is to forget about changing the
way the XML is written.  IMHO, this would be an especially compelling
choice if you are just looking to address some human-readability issue,
and the XML is not being consumed by any other program/assembly.

Pete
Peter Duniho replied to Peter Duniho on 11-Nov-11 11:00 PM
Re-reading this, I think I should clarify: by the above, I do not mean
that I believe that this actually exists in the current .NET.  Just that
it might be reasonable to request this as a feature for a future version.
Big Steel replied to csharper on 11-Nov-11 11:06 PM
What are you going to cast the XML back to as an class/object?  It would
blow-up if you tried to cast the XML back to an class/object that you
really do not have a class/object for it after you changed the XML.
Wouldn't it?

Maybe you should just keep it simple. It seems that at one point you
know what the class/object should be. You have class1, class2 or class3,
and you can cast the XML back to class1, class2 or class3.
Arne_Vajhøj replied to csharper on 12-Nov-11 02:27 PM
I am a bit skeptical about the concept, but XmLSerializer supports it.

Example:

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

namespace E
{
public class Book
{
public string Title { get; set; }
public string Author { get; set; }
}
public class Program
{
public static void Standard()
{
Book o = new Book();
o.Title = "XML Serialization is fun";
o.Author = "Arne";
XmlSerializer xmlser = new XmlSerializer(typeof(Book));
using(StreamWriter sw = new StreamWriter(@"C:\work\book.xml"))
{
xmlser.Serialize(sw, o);
}
Book o2;
using(StreamReader sr = new StreamReader(@"C:\work\book.xml"))
{
o2 = (Book)xmlser.Deserialize(sr);
}
Console.WriteLine(o2.Title + "/" + o2.Author);
}
public static void Modifying()
{
Book o = new Book();
o.Title = "XML Serialization is fun";
o.Author = "Arne";
XmlAttributes attrs = new XmlAttributes();
attrs.XmlRoot = new XmlRootAttribute("GoodBook");
XmlAttributeOverrides over = new XmlAttributeOverrides();
over.Add(typeof(Book), attrs);
XmlSerializer xmlser = new XmlSerializer(typeof(Book), over);
using(StreamWriter sw = new StreamWriter(@"C:\work\book2.xml"))
{
xmlser.Serialize(sw, o);
}
Book o2;
using(StreamReader sr = new StreamReader(@"C:\work\book2.xml"))
{
o2 = (Book)xmlser.Deserialize(sr);
}
Console.WriteLine(o2.Title + "/" + o2.Author);
}
public static void Main(string[] args)
{
Standard();
Modifying();
Console.ReadKey();
}
}
}

Arne
csharper replied to Arne_Vajhøj on 12-Nov-11 08:14 PM
Thanks. I did find out at MSDN that XmlSerializer does have a constructor that takes an XmlRootAttribute, through which we can name our root element. Arne's code also shows this. Thanks.

Things will be more complicated if a class contains a Book object like so:

public class Wraper
{
public string Name { get; set; }
public Book book { get; set; }
}

Now the said XmlSerializer constructor will not help. I guess Peter's idea of inheriting from Book will help. Thank him.

It would be nice if we could do something like this:

Book b = new Book {Title = "My Book Title", Author = "John Doe"};
b.XmlRootAttribute = new XmlRootAttribute("Libros");  // This does not exist.
Wraper w = new Wraper{ Name = "My Wraper Name", Book = b};

Then we simply create an XmlSerializer for the Wraper type and serialized it to XML and we get a Libros element.

When I need it to be TextBook, I simply say:

b.XmlRootAttribute = new XmlRootAttribute("TextBook");  // This does not exist.

Maybe an extension method using reflection? I have not tested it out, but it seems it is possible to modify the attribute through reflection. See here: http://stackoverflow.com/questions/2160476/how-to-set-attributes-values-using-reflection
Arne_Vajhøj replied to csharper on 12-Nov-11 09:05 PM
Multi level is supported as well.

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

namespace E
{
public class Book
{
public string Title { get; set; }
public string Author { get; set; }
}
public class Wrapper
{
public string Name { get; set; }
public Book Book { get; set; }
}
public class Program
{
public static void Standard()
{
Book o = new Book();
o.Title = "XML Serialization is fun";
o.Author = "Arne";
Wrapper w = new Wrapper();
w.Name = "Demo";
w.Book = o;
XmlSerializer xmlser = new XmlSerializer(typeof(Wrapper));
using(StreamWriter sw = new
StreamWriter(@"C:\work\wrapper.xml"))
{
xmlser.Serialize(sw, w);
}
Wrapper w2;
using(StreamReader sr = new
StreamReader(@"C:\work\wrapper.xml"))
{
w2 = (Wrapper)xmlser.Deserialize(sr);
}
Console.WriteLine(w2.Name + ": " + w2.Book.Title + "/" +
w2.Book.Author);
}
public static void Modifying()
{
Book o = new Book();
o.Title = "XML Serialization is fun";
o.Author = "Arne";
Wrapper w = new Wrapper();
w.Name = "Demo";
w.Book = o;
XmlAttributes attrs = new XmlAttributes();
attrs.XmlRoot = new XmlRootAttribute("SomeWrapper");
XmlAttributes attrs2 = new XmlAttributes();
attrs2.XmlElements.Add(new XmlElementAttribute("GoodBook"));
XmlAttributeOverrides over = new XmlAttributeOverrides();
over.Add(typeof(Wrapper), attrs);
over.Add(typeof(Wrapper), "Book", attrs2);
XmlSerializer xmlser = new XmlSerializer(typeof(Wrapper),
over);
using(StreamWriter sw = new
StreamWriter(@"C:\work\wrapper2.xml"))
{
xmlser.Serialize(sw, w);
}
Wrapper w2;
using(StreamReader sr = new
StreamReader(@"C:\work\wrapper2.xml"))
{
w2 = (Wrapper)xmlser.Deserialize(sr);
}
Console.WriteLine(w2.Name + ": " + w2.Book.Title + "/" +
w2.Book.Author);
}
public static void Main(string[] args)
{
Standard();
Modifying();
Console.ReadKey();
}
}
}

Arne
csharper replied to Arne_Vajhøj on 12-Nov-11 11:03 PM
That said, it seems that it is a little easier to simply inherit from Book. I am yet to research the XmlAttributeOverrides class. It seems from your code that we have to know that Book should be an immediate child of Wraper.
csharper replied to Arne_Vajhøj on 12-Nov-11 10:57 PM
Aha, that makes things easy. Thanks. Problem solved.
Arne_Vajhøj replied to csharper on 13-Nov-11 11:16 AM
I would hav ethougth that would mean more cod, but maybe not.


Yes.

The approach is somewhat typesafe/specific - you change the element
name for specific properties in specific classes.

If you want a pure textual approach, then you need to something
like this:

using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using System.Xml.Serialization;

namespace E
{
public class Book
{
public string Title { get; set; }
public string Author { get; set; }
}
public class Wrapper
{
public string Name { get; set; }
public Book Book { get; set; }
}
public class Program
{
public static void Standard()
{
Book o = new Book();
o.Title = "XML Serialization is fun";
o.Author = "Arne";
Wrapper w = new Wrapper();
w.Name = "Demo";
w.Book = o;
XmlSerializer xmlser = new XmlSerializer(typeof(Wrapper));
using(StreamWriter sw = new
StreamWriter(@"C:\work\wrapper.xml"))
{
xmlser.Serialize(sw, w);
}
Wrapper w2;
using(StreamReader sr = new
StreamReader(@"C:\work\wrapper.xml"))
{
w2 = (Wrapper)xmlser.Deserialize(sr);
}
Console.WriteLine(w2.Name + ": " + w2.Book.Title + "/" +
w2.Book.Author);
}
private class MyXmlTextWriter : XmlTextWriter
{
private Dictionary<string, string> repl;
public MyXmlTextWriter(StreamWriter sw, Dictionary<string,
string> repl) : base(sw)
{
base.Formatting = Formatting.Indented;
this.repl = repl;
}
public override void WriteStartElement(string prefix,
string localName, string ns)
{
if(repl.ContainsKey(localName))
{
base.WriteStartElement(prefix, repl[localName], ns);
}
else
{
base.WriteStartElement(prefix, localName, ns);
}
}
}
private class MyXmlTextReader : XmlTextReader
{
private Dictionary<string, string> repl;
public MyXmlTextReader(StreamReader sr, Dictionary<string,
string> repl) : base(sr)
{
this.repl = repl;
}
public override string LocalName
{
get
{
if(repl.ContainsKey(base.LocalName))
{
return repl[base.LocalName];
}
else
{
return base.LocalName;
}
}
}
}
public static void Modifying()
{
Dictionary<string, string> wrepl = new Dictionary<string,
string>();
wrepl.Add("Wrapper", "SomeWrapper");
wrepl.Add("Book", "GoodBook");
Dictionary<string, string> rrepl = new Dictionary<string,
string>();
rrepl.Add("SomeWrapper", "Wrapper");
rrepl.Add("GoodBook", "Book");
Book o = new Book();
o.Title = "XML Serialization is fun";
o.Author = "Arne";
Wrapper w = new Wrapper();
w.Name = "Demo";
w.Book = o;
XmlSerializer xmlser = new XmlSerializer(typeof(Wrapper));
using(XmlWriter xw = new MyXmlTextWriter(new
StreamWriter(@"C:\work\wrapper2.xml"), wrepl))
{
xmlser.Serialize(xw, w);
}
Wrapper w2;
using(XmlReader xr = new MyXmlTextReader(new
StreamReader(@"C:\work\wrapper2.xml"), rrepl))
{
w2 = (Wrapper)xmlser.Deserialize(xr);
}
Console.WriteLine(w2.Name + ": " + w2.Book.Title + "/" +
w2.Book.Author);
}
public static void Main(string[] args)
{
Standard();
Modifying();
Console.ReadKey();
}
}
}

Arne