How to Serialize/Deserialize Simple Classes to Xml and Back

How to serialize/deserialize simple classes to XML and back

XmlSerializer is one way to do it. DataContractSerializer is another. Example with XmlSerializer:

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

//...

ShoppingCart shoppingCart = FetchShoppingCartFromSomewhere();
var serializer = new XmlSerializer(shoppingCart.GetType());
using (var writer = XmlWriter.Create("shoppingcart.xml"))
{
serializer.Serialize(writer, shoppingCart);
}

and to deserialize it back:

var serializer = new XmlSerializer(typeof(ShoppingCart));
using (var reader = XmlReader.Create("shoppingcart.xml"))
{
var shoppingCart = (ShoppingCart)serializer.Deserialize(reader);
}

Also for better encapsulation I would recommend you using properties instead of fields in your CartItem class.

How to serialize / deserialize xml into a C# object?

I Found the solution:

public class Response
{
[System.Xml.Serialization.XmlElementAttribute("Business", typeof(ResponseBusiness), Form = System.Xml.Schema.XmlSchemaForm.Unqualified)]
public object[] Items { get; set; }
}

public partial class ResponseBusiness
{
string NumberField;

string NameField;

/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string Number
{
get
{
return this.NumberField;
}
set
{
this.NumberField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string Name
{
get
{
return this.NameField;
}
set
{
this.NameField = value;
}
}

I call the XmlSerializer class like this:

var serializer = new XmlSerializer(typeof(Response), new XmlRootAttribute("Response"));

And I can read the information like this:

string businessNumber = ((ResponseBusiness)result.Items[0]).Number;

Hope it can help someone else.

Serialize an object to XML

You have to use XmlSerializer for XML serialization. Below is a sample snippet.

 XmlSerializer xsSubmit = new XmlSerializer(typeof(MyObject));
var subReq = new MyObject();
var xml = "";

using(var sww = new StringWriter())
{
using(XmlWriter writer = XmlWriter.Create(sww))
{
xsSubmit.Serialize(writer, subReq);
xml = sww.ToString(); // Your XML
}
}

As per @kiquenet request for generic class:

public class MySerializer<T> where T : class
{
public static string Serialize(T obj)
{
XmlSerializer xsSubmit = new XmlSerializer(typeof(T));
using (var sww = new StringWriter())
{
using (XmlTextWriter writer = new XmlTextWriter(sww) { Formatting = Formatting.Indented })
{
xsSubmit.Serialize(writer, obj);
return sww.ToString();
}
}
}
}

usage:

string xmlMessage = MySerializer<MyClass>.Serialize(myObj);

Serialize/Deserialize derived class as base class

Your problem is that you are constructing your XmlSerializer inconsistently during serialization and deserialization. You need to construct it using the same Type argument in both cases, specifically the base type typeof(Device). Thus I'd suggest you replace your existing completely general serialization method with one specific for a Device:

public static class DeviceExtensions
{
public static string SerializeDevice<TDevice>(this TDevice o) where TDevice : Device
{
// Ensure that [XmlInclude(typeof(TDevice))] is present on Device.
// (Included for clarity -- actually XmlSerializer will make a similar check.)
if (!typeof(Device).GetCustomAttributes<XmlIncludeAttribute>().Any(a => a.Type == o.GetType()))
{
throw new InvalidOperationException("Unknown device type " + o.GetType());
}
var serializer = new XmlSerializer(typeof(Device)); // Serialize as the base class
using (var stringWriter = new StringWriterWithEncoding(Encoding.UTF8))
{
serializer.Serialize(stringWriter, o);
return stringWriter.ToString();
}
}

public static Device DeserializeDevice(this string xml)
{
var serial = new XmlSerializer(typeof(Device));
using (var reader = new StringReader(xml))
{
return (Device)serial.Deserialize(reader);
}
}
}

Then, apply [XmlInclude(typeof(TDevice))] to Device for all possible subtypes:

[XmlInclude(typeof(WindowsDevice))]
[XmlInclude(typeof(AndroidDevice))]
public abstract class Device
{
}

Then both types of devices can now be serialized and deserialized successfully while retaining their type, because XmlSerializer will include an "xsi:type" attribute to explicitly indicate the type:

<Device xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:type="WindowsDevice" />

Or

<Device xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:type="AndroidDevice" />

Sample fiddle.

Updates

So the issue was, that I serialized with typeof(WindowsDevice) instead of typeof(Device)?

Yes.

Any ideas for a solution which will work, if I have to use typeof(WindowsDevice)? Cause I have hundreds of classes and don't want to use hundreds of different XmlSerializer initializations...

This is more of an architectural question than a howto question. One possibility would be to introduce a custom attribute that you can apply to a class to indicate that any subtypes of that class should always be serialized as the attributed base type. All appropriate [XmlInclude(typeof(TDerivedType))] attributes will also be required:

[System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public class XmlBaseTypeAttribute : System.Attribute
{
}

[XmlInclude(typeof(WindowsDevice))]
[XmlInclude(typeof(AndroidDevice))]
[XmlBaseType]
public abstract class Device
{
}

Then modify your universal XML serialization code to look up the type hierarchy of the object being serialized for an [XmlBaseType] attribute, and (de)serialize as that type:

public static class XmlExtensions
{
static Type GetSerializedType(this Type type)
{
var serializedType = type.BaseTypesAndSelf().Where(t => Attribute.IsDefined(t, typeof(XmlBaseTypeAttribute))).SingleOrDefault();
if (serializedType != null)
{
// Ensure that [XmlInclude(typeof(TDerived))] is present on the base type
// (Included for clarity -- actually XmlSerializer will make a similar check.)
if (!serializedType.GetCustomAttributes<XmlIncludeAttribute>().Any(a => a.Type == type))
{
throw new InvalidOperationException(string.Format("Unknown subtype {0} of type {1}", type, serializedType));
}
}
return serializedType ?? type;
}

public static string Serialize(this object o)
{
var serializer = new XmlSerializer(o.GetType().GetSerializedType());
using (var stringWriter = new StringWriterWithEncoding(Encoding.UTF8))
{
serializer.Serialize(stringWriter, o);
return stringWriter.ToString();
}
}

public static T Deserialize<T>(this string xml)
{
var serial = new XmlSerializer(typeof(T).GetSerializedType());
using (var reader = new StringReader(xml))
{
return (T)serial.Deserialize(reader);
}
}
}

Of course this means that if your code tries to deserialize XML it expects to contain a WindowsDevice, it might actually get back an AndroidDevice depending upon the contents of the XML.

Sample fiddle #2.

How can I make XmlSerializer Deserialize tell me about typos on tag names

Having looked at Microsoft's published source code for XmlSerializer, it became apparent that there are events that you can subscribe to (which is what I was hoping for); but they aren't exposed on the XmlSerializer itself... you have to inject a struct containing them into the constructor.

So I've been able to modify the code from the question to have an event handler which gets called when an unknown node is encountered (which is exactly what I was after). You need one extra using, over the ones given in the question...

using System.Xml;

and then here is the modified "Load" method...

    private static Example Load(string serialized)
{
XmlDeserializationEvents events = new XmlDeserializationEvents();
events.OnUnknownNode = (sender, e) => System.Diagnostics.Debug.WriteLine("Unknown Node: " + e.Name);

var xmlSerializer = new XmlSerializer(typeof(Example));
using var reader = XmlReader.Create(new StringReader(serialized));
return (Example)xmlSerializer.Deserialize(reader, events);
}

So now I just need to do something more valuable than just write a line to the Debug output.

Note that more events are available, as described on the XmlDeserializationEvents page, and I'll probably pay attention to each of them.

Best Practice for Serialize/Deserialize from Java to XML

I've always had positive experiences with XStream:

http://x-stream.github.io/tutorial.html#to-xml

As you can see, it's simple to use.

I haven't actually used XStream with Generics (I've only ever used it for simple JavaBean type classes), but Google seems to suggest it handles them without problems. e.g. http://techo-ecco.com/blog/xstream-spring-ws-oxm-and-generics/

Preserve Xml Xsd schemaLocation when deserializing then serializing

You can deserialize and re-serialize the xsi:schemaLocation attribute by adding an explicit property for that purpose, as shown in this answer by dtb to XmlSerialization and xsi:SchemaLocation (xsd.exe), suitably translated to VB.NET:

Public Partial Class XmlStationSetpoints
<XmlAttribute("schemaLocation", Namespace:="http://www.w3.org/2001/XMLSchema-instance")>
Public Property xsiSchemaLocation As String = "http://www.w3schools.com StationSetpoints.xsd"
End Class

Demo fiddle #1 here.

If you would like to hardcode the value of xsi:schemaLocation and have it appear unconditionally when serializing, you may do so with an explicitly implemented property like so:

Public Partial Class XmlStationSetpoints
<XmlAttribute("schemaLocation", Namespace:="http://www.w3.org/2001/XMLSchema-instance")>
Public Property xsiSchemaLocation() As String
Get
Return "http://www.w3schools.com StationSetpoints.xsd"
End Get
Set
' Do nothing, it's hardcoded. A setter is required because XmlSerializer will only serialize properties with public getters and setters.
End Set
End Property
End Class

Demo fiddle #2 here.

And if you would like to hardcode the value of xsi:schemaLocation but control whether or not it appears (because e.g. it should only appear when XmlStationSetpoints is the root element of the XML file) you may do so with an xsiSchemaLocationSpecified property like so:

Public Partial Class XmlStationSetpoints
<XmlAttribute("schemaLocation", Namespace:="http://www.w3.org/2001/XMLSchema-instance")>
Public Property xsiSchemaLocation() As String
Get
Return "http://www.w3schools.com StationSetpoints.xsd"
End Get
Set
' Do nothing, it's hardcoded. A setter is required because XmlSerializer will only serialize properties with public getters and setters.
End Set
End Property
<XmlIgnore>
Public Property xsiSchemaLocationSpecified() As Boolean
End Class

The property will capture whether the xsi:schemaLocation property was encountered during deserialization, and will control whether the property is emitted during serialization.

Demo fiddle #3 here.



Related Topics



Leave a reply



Submit