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
How to Get the Local Network Ip Address of a Computer Programmatically
Linq Query Group by and Selecting First Items
Differencebetween .Equals and ==
Why Is This Process Crashing as Soon as It Is Launched
How to Use Datareceived Event of the Serialport Port Object in C#
How to Calculate the Average Rgb Color Values of a Bitmap
Error: Must Create Dependencysource on Same Thread as the Dependencyobject Even by Using Dispatcher
Catching Unhandled Exception on Separate Threads
A Reproducible Example of Volatile Usage
How to Concatenate Two System.Io.Stream Instances into One
Copy Values from One Object to Another
How to Detect the Original MAC Address After It Has Been Spoofed
Iequalitycomparer for Sequenceequal
Linq to Find Series of Consecutive Numbers
How to Print PDF on Default Network Printer Using Ghostscript (Gswin32C.Exe) Shell Command