XML serialization in .net - c#

I'm trying to serialize an object to meet another systems requirements.
It need to look like this:
<custom-attribute name="Colour" dt:dt="string">blue</custom-attribute>
but instead is looking like this:
<custom-attribute>blue</custom-attribute>
So far I have this:
[XmlElement("custom-attribute")]
public String Colour{ get; set; }
I'm not really sure what metadata I need to achieve this.

You could implement IXmlSerializable:
public class Root
{
[XmlElement("custom-attribute")]
public Colour Colour { get; set; }
}
public class Colour : IXmlSerializable
{
[XmlText]
public string Value { get; set; }
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
throw new NotImplementedException();
}
public void WriteXml(XmlWriter writer)
{
writer.WriteAttributeString("dt:dt", "", "string");
writer.WriteAttributeString("name", "Colour");
writer.WriteString(Value);
}
}
class Program
{
static void Main()
{
var serializer = new XmlSerializer(typeof(Root));
var root = new Root
{
Colour = new Colour
{
Value = "blue"
}
};
serializer.Serialize(Console.Out, root);
}
}

Related

How to save the Interface collections in xml in wpf

I have a Model with interface collection. I want to save the collection in xml file at runtime in temp location. Without interface collection the Model is saved correctly in xml file. But the interface collection in not saved in xml file. Please anyone help me to achieve this. My Model class structure is mentioned below,
MainWindowModel
public class MainWindowModel
{
private string header;
public string Header
{
get { return header; }
set { header = value; }
}
private bool isEditing = false;
public bool IsEditing
{
get { return isEditing; }
set { isEditing = value; }
}
public ObservableCollection<Details> DetailsCollection { get; set; }
}
Details
public class Details
{
public string Key { get; set; }
public ObservableCollection<IValue> Values { get; set; }
}
IValue
public interface IValue
{
int Id { get; set; }
string Name { get; set; }
}
FileReaderWriter
public class FileReaderWriter<T>
{
public string FileLocation;
public T Fetch()
{
if (File.Exists(FileLocation))
{
XmlSerializer deserializer = new XmlSerializer(typeof(T));
TextReader reader = new StreamReader(FileLocation);
object obj = deserializer.Deserialize(reader);
T XmlData = (T)obj;
reader.Close();
return XmlData;
}
return default(T);
}
public virtual string GetFileLocation()
{
return FileLocation;
}
public void Save(T model)
{
XmlSerializer serializer = new XmlSerializer(typeof(T));
var directory = Path.GetDirectoryName(FileLocation);
if (!string.IsNullOrEmpty(directory))
{
if (!Directory.Exists(directory))
Directory.CreateDirectory(directory);
using (var writer = XmlWriter.Create(FileLocation))
{
serializer.Serialize(writer, model);
}
}
}
}
ReaderWriterClass
public class DetailsViewReaderWriter : FileReaderWriter<ObservableCollection<MainWindowModel>>
{
public DetailsViewReaderWriter()
{
FileLocation = ConfigurationManager.AppSettings["RecentFileLocation"];
}
public ObservableCollection<MainWindowModel> FetchFile()
{
var recentFile = Fetch();
return recentFile;
}
public override string GetFileLocation()
{
return FileLocation;
}
public void SaveFile(ObservableCollection<MainWindowModel> fileModel)
{
Save(fileModel);
}
}
App.config
<appSettings>
<add key="RecentFileLocation" value="D:\MyProject\RecentDetails.xml"/>
</appSettings>
The XmlSerializer cannot serialize Interface types. What you can do is to implement the IXmlSerializable interface. Please find below a rough! example. It is not tested. You should read carefully on how to implement the IXmlSerializable interface correctly. But to give you an idea and help to get started:
Adjust your IValue interface:
public interface IValue: IXmlSerializable
{
int Id { get; set; }
string Name { get; set; }
}
Implement the interface:
public class Value : IValue
{
public int Id { get; set; }
public string Name { get; set; }
public XmlSchema GetSchema() => null;
public void ReadXml(XmlReader reader)
{
reader.MoveToContent();
Name = reader.GetAttribute("Name");
Id = int.Parse(reader.GetAttribute("Id"));
}
public void WriteXml(XmlWriter writer)
{
writer.WriteAttributeString("Id", Id.ToString());
writer.WriteAttributeString("Name", Name);
}
}

XML Deserialization Rules

I need to deserialize xml file and its structured this way:
<NPCs>
<LabAssistant1>
<Questions>
<Question>
<Type>CheckBox</Type>
<Points>10</Points>
<Text>Q1</Text>
<Answers>
<Answer>
<Correct>False</Correct>
<Text>A1</Text>
</Answer>
<Answer>
<Correct>True</Correct>
<Text>A2</Text>
</Answer>
<Answer>
<Correct>False</Correct>
<Text>A3</Text>
</Answer>
</Answers>
</Question>
</Questions>
</LabAssistant1>
<LabAssistant2>
<Questions>
...
</Questions>
</LabAssistant2>
</NPCs>
So as you can see am having root node NPCs and my goal is to read questions separately by LabAssistant1 name or any tag name in NPCs.
String questionsPath = path+"/questions.xml";
XmlReader reader=XmlReader.Create(new StreamReader(questionsPath));
XmlRootAttribute xmlRoot = new XmlRootAttribute();
xmlRoot.ElementName = npc;
reader.ReadToDescendant(npc);
XmlSerializer se = new XmlSerializer(typeof(Question[]),xmlRoot);
Question[] qs=se.Deserialize(reader) as Question[];
Console.WriteLine(qs.Length.ToString()); // Always 0
Above code should output 2 objects of Question as array, but it doesn't
Here are the classes Question and Answer, anything is wrong with my attached attributes?
public class Question
{
[XmlElement(ElementName="Text")]
public String Text { get; set; }
[XmlArray(ElementName = "Answers")]
public Answer[] Answers { get; set; }
[XmlElement(ElementName = "Type")]
public QuestionType Type { get; set; }
[XmlElement(ElementName = "Points")]
public int Points { get; set; }
public Question()
{
}
public Question(String text, Answer[] answers, QuestionType type,int points)
{
this.Text = text;
this.Answers = answers;
this.Type = type;
this.Points = points;
}
}
public class Answer
{
[XmlElement(ElementName="Text")]
public String Text { get; set; }
[XmlElement(ElementName = "Correct")]
public bool Correct { get; set; }
public Answer()
{
}
public Answer(String text, bool correct)
{
this.Text = text;
this.Correct = correct;
}
}
You could use the UnknownElement event of XmlSerializer to load all the lab assistants into memory, like so:
public class LabAssistant
{
static XmlSerializer listSerializer;
static LabAssistant()
{
// This must be cached to prevent memory & resource leaks.
// See http://msdn.microsoft.com/en-us/library/System.Xml.Serialization.XmlSerializer%28v=vs.110%29.aspx
listSerializer = new XmlSerializer(typeof(List<Question>), new XmlRootAttribute("Questions"));
}
public List<Question> Questions { get; set; }
public static bool TryDeserializeFromXml(XmlElement element, out string name, out LabAssistant assistant)
{
name = element.Name;
var child = element.ChildNodes.OfType<XmlElement>().Where(el => el.Name == "Questions").FirstOrDefault();
if (child != null)
{
var list = child.OuterXml.LoadFromXML<List<Question>>(listSerializer);
if (list != null)
{
assistant = new LabAssistant() { Questions = list };
return true;
}
}
assistant = null;
return false;
}
}
public class NPCs
{
public NPCs()
{
this.LabAssistants = new Dictionary<string, LabAssistant>();
}
public static XmlSerializer CreateXmlSerializer()
{
// No need to cache this.
var serializer = new XmlSerializer(typeof(NPCs));
serializer.UnknownElement += new XmlElementEventHandler(NPCs.XmlSerializer_LoadLabAssistants);
return serializer;
}
[XmlIgnore]
public Dictionary<string, LabAssistant> LabAssistants { get; set; }
public static void XmlSerializer_LoadLabAssistants(object sender, XmlElementEventArgs e)
{
var obj = e.ObjectBeingDeserialized;
var element = e.Element;
if (obj is NPCs)
{
var npcs = (NPCs)obj;
string name;
LabAssistant assistant;
if (LabAssistant.TryDeserializeFromXml(element, out name, out assistant))
npcs.LabAssistants[name] = assistant;
}
}
}
Using the following helper methods:
public static class XmlSerializationHelper
{
public static T LoadFromXML<T>(this string xmlString)
{
return xmlString.LoadFromXML<T>(new XmlSerializer(typeof(T)));
}
public static T LoadFromXML<T>(this string xmlString, XmlSerializer serial)
{
T returnValue = default(T);
using (StringReader reader = new StringReader(xmlString))
{
object result = serial.Deserialize(reader);
if (result is T)
{
returnValue = (T)result;
}
}
return returnValue;
}
}
Having done this, you now have a dictionary of lab assistants by name.
While this code will deserialize your data correctly, it won't reserialize it. Custom code to serialize the dictionary would be required.
One final note - XmlSerializer will choke on the XML you provided because it requires that Boolean values be in lowercase. Thus the following will throw an exception:
<Correct>False</Correct>
If you did not mistype the XML and it really contains Booleans in this format, you will need to manually handle these fields.
I needed to create QuestionCollection class to hold the array of questions (having typeof(Question[]) throws <TagName xmlns="> was not expected, probably because the deserializer is not smart enough).
What i do next is first reading to tag LabAssistant or any tag name, next reading to its child Questions tag and after that i deserialize the questions into QuestionCollection, so with ReadToDescendant I can access any child elements of the NPCs
String questionsPath = Application.dataPath + "/Resources/questions.xml";
XmlReader reader=XmlReader.Create(new StreamReader(questionsPath));
reader.ReadToDescendant("LabAssistant");
reader.ReadToDescendant("Questions");
XmlSerializer se = new XmlSerializer(typeof(QuestionCollection));
QuestionCollection qc=(QuestionCollection)se.Deserialize(reader);
QuestionCollection class:
[XmlType("Questions")]
public class QuestionCollection
{
[XmlElement("Question")]
public Question[] Questions { get; set; }
public QuestionCollection() { }
}
Question class
[XmlType("Question")]
public class Question
{
[XmlElement("Text")]
public String Text { get; set; }
[XmlArray("Answers")]
public Answer[] Answers { get; set; }
[XmlElement("Type")]
public QuestionType Type { get; set; }
[XmlElement("Points")]
public int Points { get; set; }
public Question() { }
}
Answer class:
[XmlType("Answer")]
public class Answer
{
[XmlElement("Text")]
public String Text { get; set; }
[XmlElement("Correct")]
public bool Correct { get; set; }
public Answer() { }
}

How to serialize List<IFilter> (Nokia Imaging SDK)?

I'm trying to save a List of IFilter(of type Interface) which are applied to an image using XML serialization, so that user can edit the same image from where he left off.
[XmlRoot]
public class ImageProperties
{
public string ImageName { get; set; }
public string ImageFilePath { get; set; }
public List<IFilter> Filters { get; set; }
}
Is this possible? Is there another alternative to do the same?
You could use IXmlSerializable to achieve this.. assuming you can change the ImageProperties class.
Upon serialization, you can get the type by looking at each filter instance and querying for it. You can store this type information in the XML so when you come to read it, you know which type it is, and you can then just invoke the default XML serializer for each filter.
Here is a possible implementation.
public class ImageProperties : IXmlSerializable
{
public string ImageName { get; set; }
public string ImageFilePath { get; set; }
public List<IFilter> Filters { get; set; }
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
string startEle = reader.Name;
reader.ReadStartElement();
Filters = new List<IFilter>();
do
{
switch (reader.Name)
{
case "imgName":
ImageName = reader.ReadElementContentAsString();
break;
case "imgFilePath":
ImageFilePath = reader.ReadElementContentAsString();
break;
case "filters":
reader.ReadStartElement("filters");
while (reader.Name.Equals("iFilter"))
{
XmlSerializer filterSerializer = new XmlSerializer(Type.GetType(reader.GetAttribute("type")));
reader.ReadStartElement("iFilter");
Filters.Add((IFilter)filterSerializer.Deserialize(reader));
reader.ReadEndElement();
}
reader.ReadEndElement();
break;
default:
reader.ReadOuterXml();
break;
}
} while (reader.Name != startEle);
}
public void WriteXml(System.Xml.XmlWriter writer)
{
writer.WriteElementString("imgName", ImageName);
writer.WriteElementString("imgFilePath", ImageFilePath);
writer.WriteStartElement("filters");
foreach (IFilter filter in Filters)
{
writer.WriteStartElement("iFilter");
writer.WriteAttributeString("type", filter.GetType().FullName);
XmlSerializer filterSerializer = new XmlSerializer(filter.GetType());
filterSerializer.Serialize(writer, filter);
writer.WriteEndElement();
}
writer.WriteEndElement();
}
}
If you have different types of filter, the default serializer of the real type is invoked, so their unique properties will get recorded.
For example, with these filter classes available:
public interface IFilter
{
string SomeCommonProp { get; set;}
}
[XmlRoot("myFilter")]
public class MyFilter : IFilter
{
[XmlElement("somemyFilterProp")]
public string SomeMyFilterProp { get; set; }
[XmlElement("someCommonProp")]
public string SomeCommonProp { get; set;}
}
[XmlRoot("myOtherFilter")]
public class MyOtherFilter : IFilter
{
[XmlElement("someOtherFilterProp")]
public string SomeOtherFilterProp { get; set; }
[XmlElement("someCommonProp")]
public string SomeCommonProp { get; set;}
}
You can use as follows to serialise and deserialise two different types of filter in IFilters to xml.
static void Main(string[] args)
{
ImageProperties props = new ImageProperties();
props.ImageName = "img.png";
props.ImageFilePath = "c:\\temp\\img.png";
props.Filters = new List<IFilter>();
props.Filters.Add(new MyFilter() { SomeMyFilterProp = "x", SomeCommonProp ="p" });
props.Filters.Add(new MyOtherFilter() { SomeOtherFilterProp = "y", SomeCommonProp ="p" });
XmlSerializer s = new XmlSerializer(typeof(ImageProperties));
using (StreamWriter writer = new StreamWriter(#"c:\temp\imgprops.xml"))
s.Serialize(writer, props);
using (StreamReader reader = new StreamReader(#"c:\temp\imgprops.xml"))
{
object andBack = s.Deserialize(reader);
}
Console.ReadKey();
}
This produces an XML that looks like this.
<?xml version="1.0" encoding="utf-8"?>
<ImageProperties>
<imgName>img.png</imgName>
<imgFilePath>c:\temp\img.png</imgFilePath>
<filters>
<iFilter type="SomeNameSpace.Whatever.MyFilter">
<myFilter xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<somemyFilterProp>x</somemyFilterProp>
<someCommonProp>p</someCommonProp>
</myFilter>
</iFilter>
<iFilter type="SomeNameSpace.Whatever.MyOtherFilter">
<myOtherFilter xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<someOtherFilterProp>y</someOtherFilterProp>
<someCommonProp>p</someCommonProp>
</myOtherFilter>
</iFilter>
</filters>
</ImageProperties>
No. Interface instances cannot be serialized. It doesn't know the implementation to "deserialize" to. It will require a concrete class or custom serialization in this case.

Removing Parent XML element from XML Serialization in C#?

I have a class named Node and inside that i have Property of Type Document Class.
When I serialize it into XML, I get the output as
<Node>
<DocumentType>
<File></File>
</DoumentType>
<Node>
But I want the output as
<Node>
<File></File>
<Node>
Object Code
public class Document
{
[XmlElement(ElementName = "file")]
public string File { get; set; }
}
public class Node
{
public Document NodeDocument
{
get;
set;
}
}
How can I do that using C# xml Serialization?
Following Kami's suggestion, here is the code for your reference. All credit goes to Kami.
public class Node : IXmlSerializable {
public Node() {
NodeDocument = new Document();
}
public Document NodeDocument { get; set; }
public System.Xml.Schema.XmlSchema GetSchema() {
return null;
}
public void ReadXml(XmlReader reader) {
reader.ReadStartElement();
NodeDocument.File = reader.ReadString();
reader.ReadEndElement();
}
public void WriteXml(XmlWriter writer) {
writer.WriteStartElement("file");
writer.WriteString(NodeDocument.File);
writer.WriteEndElement();
}
}
public class Document {
public String File { get; set; }
}
class Program {
static void Main(string[] args) {
var node = new Node();
node.NodeDocument.File = "bbb.txt";
Serialize<Node>("a.xml", node);
node = Deserialize<Node>("a.xml");
Console.WriteLine(node.NodeDocument.File);
Console.Read();
}
static T Deserialize<T>(String xmlFilePath) where T : class {
using (var textReader = File.OpenText(xmlFilePath)) {
using (var xmlTextReader = new XmlTextReader(textReader)) {
var serializer = new XmlSerializer(typeof(T));
return (T)serializer.Deserialize(xmlTextReader);
}
}
}
static void Serialize<T>(String xmlFilePath, T obj) where T : class {
using (var textWriter = File.CreateText(xmlFilePath)) {
using (var xmlTextWriter = new XmlTextWriter(textWriter)) {
var serializer = new XmlSerializer(typeof(T));
serializer.Serialize(xmlTextWriter, obj);
}
}
}
}
Have you considered implementing IXmlSerializable interface - http://msdn.microsoft.com/en-us/library/system.xml.serialization.ixmlserializable.aspx.
You should then be able to write custom serialization/deserialization to facilitate the above.

Derived Class Deserialization

I have a problem with deserialization with my logic simulation program.
Here are my element classes:
public class AndGateData : TwoInputGateData
{
}
public class TwoInputGateData : GateData
{
public TwoInputGateData()
{
Input2 = new InputData();
Input1 = new InputData();
}
public InputData Input1 { get; set; }
public InputData Input2 { get; set; }
}
public class GateData : ElementData
{
public GateData()
{
OutputData = new OutputData();
}
public OutputData OutputData { get; set; }
}
public class ElementData
{
public int Delay { get; set; }
public Guid Id { get; set; }
}
And here are classes responsible for sockets:
public class InputData : SocketData
{
}
public class SocketData
{
public Guid Id { get; set; }
public SignalData SignalData { get; set; }
}
SignalData is not important here. So, I won't write it (in order to keep this question clean) here unless somebody says it is necessary.
CircuitData is very important:
[XmlRoot("Circuit")]
public class CircuitData
{
[XmlElement(typeof(AndGateData))]
[XmlElement(typeof(OrGateData))]
public List<ElementData> elements = new List<ElementData>();
public List<WireData> wires = new List<WireData>();
public void AddElement(ElementData element)
{
elements.Add(element);
}
public void AddWire(WireData wire)
{
wires.Add(wire);
}
}
Wires are not important right now.
Now, I have written some Serialization:
public class CircuitDataWriter
{
public static void Write(object obj, string fileName)
{
var xmlFormat = new XmlSerializer(typeof(CircuitData));
using(Stream fStream = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None) )
{
xmlFormat.Serialize(fStream,obj);
}
Console.WriteLine("Circuit saved in XML format.");
}
}
It works just like I wanted, it produces that xml document:
<?xml version="1.0"?>
-<Circuit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
-<AndGateData>
<Delay>10</Delay>
<Id>bfee6dd7-5946-4b7b-9d0b-15d5cf60e2bf</Id>
-<OutputData> <Id>00000000-0000-0000-0000-000000000000</Id> </OutputData>
-<Input1> <Id>7c767caf-79a9-4c94-9e39-5c38ec946d1a</Id> <SignalData xsi:type="SignalDataOn"/> </Input1>
-<Input2> <Id>d2cad8f8-8528-4db3-9534-9baadb6a2a14</Id> <SignalData xsi:type="SignalDataOff"/> </Input2>
</AndGateData>
<wires/>
</Circuit>
But I have problem with my DESERIALIZATION. Here is the code:
public static CircuitData Read()
{
var reader = new XmlSerializer(typeof(CircuitData));
StreamReader file = new StreamReader("Circuit.xml");
var returnCircuitData = new CircuitData();
returnCircuitData = (CircuitData) reader.Deserialize(file);
return returnCircuitData;
}
Now, it deserializes my Circuit.xml to object, but this object only contains Id and Delay, it does not contain Input1, Input2 or Output. So, it is treated like Element, not like AndGate. I tried to solve it out for a day but it seems that no one has that kind of problem.
I have a suggestion for you, make the Write method generic like this and create the serializer using objectToSerialize.GetType():
public static void Write<T>(T objectToSerialize, string fileName)
{
var xmlSerializer = new XmlSerializer(objectToSerialize.GetType());
...
}
The XmlSerializer.Deserialize() method returns object, you can make your Read method generic like this:
public static T Read<T>(string fileName)
{
var serializer = new XmlSerializer(typeof(T));
using (StreamReader file = new StreamReader(fileName))
{
return (T)serializer.Deserialize(file);
}
}
Other than that you might want to read about:
XmlInclude that is used when you serialize derived classes.
XmlArray and XmlArrayItem that are used for controlling serialization of arrays

Categories

Resources