Class Serialization To XML - c#

Given the following class design:
public class AllUserCollections
{
public List<UserCollection> UserCollections { get; set; }
public AllUserCollections()
{
this.UserCollections = new List<UserCollection> ();
}
}
public class UserCollection
{
public string UserGroup { get; set; }
public Dictionary<int,User> Users { get; set; }
public UserCollection(string userGroup)
{
this.UserGroup = userGroup;
this.Users = new Dictionary<int, User> ();
}
}
public class User
{
public int ID { get; set; }
public string Name { get; set; }
public string Location { get; set; }
public AgeGroup UserAgeGroup { get; set; }
}
public enum AgeGroup
{
Twenties,
Thirties,
Fourties,
}
How can I serialize this to XML using my existing serialization class?
public static class HardDriveService
{
private static string docsFolderPath = Environment.GetFolderPath (Environment.SpecialFolder.Personal);
private const string fileName = "AllUserCollections.xml";
private static string filePath = Path.Combine(docsFolderPath, fileName);
private static bool FileExists(string fullFilePath)
{
if (File.Exists (fullFilePath))
return true;
return false;
}
public static void Save(AllUserCollections allUserCollections)
{
if (FileExists(filePath))
{
File.Delete (filePath);
}
XmlSerializer serializer = new XmlSerializer(allUserCollections.GetType());
using(StreamWriter writer = new StreamWriter(filePath))
{
serializer.Serialize(writer.BaseStream, allUserCollections);
}
}
public static AllUserCollections Read()
{
AllUserCollections allUserCollections = new AllUserCollections();
XmlSerializer serializer = new XmlSerializer(allUserCollections.GetType());
if (FileExists(filePath))
{
StreamReader reader = new StreamReader(filePath);
object deserialized = serializer.Deserialize(reader.BaseStream);
allUserCollections = (AllUserCollections)deserialized;
}
return allUserCollections;
}
}//End of class.
ISSUES
My code seems to fail on this line -
XmlSerializer serializer = new XmlSerializer(allUserCollections.GetType());
I wonder if it's to do with the class needing to be explicitly marked as "serializable"? How would I do this?
Usage
This code will run on an iphone and save/read directly from an app to XML on the iPhone hard drive.

XMLSerializer doesn't support Dictionary out of the box. Your UserCollection class has a Dictionary. See this link for a workaround.
Why doesn't XmlSerializer support Dictionary?
Other than that the XMLSerializer requires that your classes have default constructors (UserCollection and User don't) and each of them must have the [Serializable] attribute.

You could use XElement to build up an XML format. You could use them along the following lines:
public static XElement ToXml(this User user)
{
if (user == null)
{
throw new ArgumentException("User can not be null.");
}
XElement userElement = new XElement("User");
userElement.Add(new XElement("ID", user.ID));
userElement.Add(new XElement("Name", user.Name));
userElement.Add(new XElement("Location", user.Location));
userElement.Add(new XElement("UserAgeGroup", user.UserAgeGroup));
return userElement;
}
public static string ToXml(this UserCollection userCollection)
{
if (userCollection == null)
{
throw new ArgumentException("UserCollection can not be null.");
}
XElement userCollectionElement = new XElement("UserCollection");
userCollectionElement.Add(new XElement("UserGroup", userCollection.UserGroup));
userCollectionElement.Add(new XElement("Users",
userCollection.Users.Select(x => new XElement("User", x.ToXml()));
return userCollectionElement;
}
Calling .ToString() on an XElement should give you an xml-formatted string.

FULL WORKING SOLUTION
Data Model
using System;
using System.Collections.Generic;
namespace iPhoneHardDriveCRUDPrototype
{
[Serializable]
public class AllUserCollections
{
public List<UserCollection> UserCollections { get; set; }
public AllUserCollections()
{
this.UserCollections = new List<UserCollection> ();
}
}
[Serializable]
public class UserCollection
{
public string UserGroup { get; set; }
public SerializableDictionary<int,User> Users { get; set; }
public UserCollection()
{
this.Users = new SerializableDictionary<int, User> ();
}
public UserCollection(string userGroup)
{
this.UserGroup = userGroup;
this.Users = new SerializableDictionary<int, User> ();
}
}
[Serializable]
public class User
{
public int ID { get; set; }
public string Name { get; set; }
public string Location { get; set; }
public AgeGroup UserAgeGroup { get; set; }
public User()
{
}
}
[Serializable]
public enum AgeGroup
{
Twenties,
Thirties,
Fourties,
}
}
Serializable Dictionary
using System;
using System.Xml.Serialization;
using System.Collections.Generic;
namespace iPhoneHardDriveCRUDPrototype
{
[XmlRoot("dictionary")]
public class SerializableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, IXmlSerializable
{
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
XmlSerializer keySerializer = new XmlSerializer(typeof(TKey));
XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue));
bool wasEmpty = reader.IsEmptyElement;
reader.Read();
if (wasEmpty)
return;
while (reader.NodeType != System.Xml.XmlNodeType.EndElement)
{
reader.ReadStartElement("item");
reader.ReadStartElement("key");
TKey key = (TKey)keySerializer.Deserialize(reader);
reader.ReadEndElement();
reader.ReadStartElement("value");
TValue value = (TValue)valueSerializer.Deserialize(reader);
reader.ReadEndElement();
this.Add(key, value);
reader.ReadEndElement();
reader.MoveToContent();
}
reader.ReadEndElement();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
XmlSerializer keySerializer = new XmlSerializer(typeof(TKey));
XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue));
foreach (TKey key in this.Keys)
{
writer.WriteStartElement("item");
writer.WriteStartElement("key");
keySerializer.Serialize(writer, key);
writer.WriteEndElement();
writer.WriteStartElement("value");
TValue value = this[key];
valueSerializer.Serialize(writer, value);
writer.WriteEndElement();
writer.WriteEndElement();
}
}
}//End of Class....
}
Serializer
using System;
using System.IO;
using System.Xml.Serialization;
using System.Reflection;
using System.Collections.Generic;
namespace iPhoneHardDriveCRUDPrototype
{
public static class HardDriveService
{
private static string docsFolderPath = Environment.GetFolderPath (Environment.SpecialFolder.Personal);
private const string fileName = "AllUserCollections.xml";
private static string filePath = Path.Combine(docsFolderPath, fileName);
private static bool FileExists(string fullFilePath)
{
if (File.Exists (fullFilePath))
return true;
return false;
}
public static void Save(AllUserCollections allUserCollections)
{
if (FileExists(filePath))
{
File.Delete (filePath);
}
XmlSerializer serializer = new XmlSerializer(allUserCollections.GetType());
using(StreamWriter writer = new StreamWriter(filePath))
{
serializer.Serialize(writer.BaseStream, allUserCollections);
}
}
public static AllUserCollections Read()
{
AllUserCollections allUserCollections = new AllUserCollections();
XmlSerializer serializer = new XmlSerializer(allUserCollections.GetType());
if (FileExists(filePath))
{
StreamReader reader = new StreamReader(filePath);
object deserialized = serializer.Deserialize(reader.BaseStream);
allUserCollections = (AllUserCollections)deserialized;
}
return allUserCollections;
}
}//End of class.
}

Here you have 2 choices XmlSerializer(which will not work with Dictionary or List type deserialization ) or you can use DataContractSerializer which added in .net 3.0 and has a lot advantages here are few: form this post
optimized for speed (about 10% faster than XmlSerializer, typically)
in "opt-in" - only stuff you specifically mark as [DataMember] will be serialized
doesn't support XML attributes (for speed reasons)
Note : you should add reference to the C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework.NETFramework\v4.0\System.Runtime.Serialization.dll
//to serialize
SerializeHelper.Serialize("your filename" ,new AllUserCollections());
// deserialize
var usertCollections = SerializeHelper.Deserialize<AllUserCollections>("yourfile name");
//code
[DataContract]
public class AllUserCollections
{
public List<UserCollection> UserCollections { get; set; }
public AllUserCollections()
{
this.UserCollections = new List<UserCollection>();
}
}
[DataContract()]
public class UserCollection
{
[DataMember]
public string UserGroup { get; set; }
[DataMember]
public Dictionary<int, User> Users { get; set; }
public UserCollection(string userGroup)
{
this.UserGroup = userGroup;
this.Users = new Dictionary<int, User>();
}
}
[DataContract()]
public class User
{ [DataMember]
public int ID { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public string Location { get; set; }
[DataMember]
public AgeGroup UserAgeGroup { get; set; }
}
[DataContract]
public enum AgeGroup
{
Twenties,
Thirties,
Fourties,
}
public static class SerializeHelper
{
public static void Serialize<T>(string fileName, T obj)
{
using (FileStream writer = new FileStream(fileName, FileMode.Create))
{
DataContractSerializer ser =
new DataContractSerializer(typeof(T));
ser.WriteObject(writer, obj);
writer.Close();
}
}
public static T Deserialize<T>(string fileName)
{
T des;
using (FileStream fs = new FileStream(fileName,FileMode.Open))
{
XmlDictionaryReader reader =
XmlDictionaryReader.CreateTextReader(fs, new XmlDictionaryReaderQuotas());
DataContractSerializer ser = new DataContractSerializer(typeof(T));
des =
(T)ser.ReadObject(reader, true);
reader.Close();
fs.Close();
}
return des;
}
}

Related

custom Xml Serializer to make value as an xml element

Classes:
public class Employee
{
public int Id { get; set; }
public PhoneNumber[] Numbers { get; set; }
}
public class PhoneNumber
{
public string Type { get; set; }
public string Number { get; set; }
}
Code to serialize:
var xmlSerializer = new XmlSerializer(typeof(Employee));
var xwSettings = new XmlWriterSettings {Indent = true, OmitXmlDeclaration = true};
string serializedResult;
using (var stream = new StringWriter())
using (var writer = XmlWriter.Create(stream, xwSettings))
{
xmlSerializer.Serialize(writer, emp);
serializedResult = stream.ToString();
}
Current Result:
<Employee>
<Id>1</Id>
<Numbers>
<PhoneNumber>
<Type>Home</Type>
<Number>1231231231</Number>
</PhoneNumber>
<PhoneNumber>
<Type>Office</Type>
<Number>3453453453</Number>
</PhoneNumber>
</Numbers>
</Employee>
Desired Result:
<Employee>
<Id>1</Id>
<Numbers>
<Home>1231231231</Home>
<Office>3453453453</Office>
</Numbers>
</Employee>
PhoneNumber Type can be added dynamically like "GuestRoomPhone" etc, so adding properties for each phone number type is not an option.
You can do this by implementing the IXmlSerializable interface on your classes. This allows you to control how the values are written and read.
public class Employee : IXmlSerializable
{
public int Id { get; set; }
public PhoneNumber[] Numbers { get; set; }
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
reader.ReadStartElement("Employee");
reader.ReadStartElement("Id");
Id = reader.ReadContentAsInt();
reader.ReadEndElement(); // Id
reader.ReadStartElement("Numbers");
List<PhoneNumber> numbers = new List<PhoneNumber>();
while (reader.MoveToContent() == XmlNodeType.Element)
{
PhoneNumber num = new PhoneNumber();
num.ReadXml(reader);
numbers.Add(num);
}
Numbers = numbers.ToArray();
reader.ReadEndElement(); // Numbers
reader.ReadEndElement(); // Employee
}
public void WriteXml(XmlWriter writer)
{
writer.WriteStartElement("Id");
writer.WriteValue(Id);
writer.WriteEndElement();
writer.WriteStartElement("Numbers");
foreach (PhoneNumber num in Numbers)
{
num.WriteXml(writer);
}
writer.WriteEndElement(); // Numbers
}
}
Similarly for the PhoneNumber class.
public class PhoneNumber : IXmlSerializable
{
public string Type { get; set; }
public string Number { get; set; }
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
while (!reader.IsStartElement())
reader.Read();
Type = reader.Name;
Number = reader.ReadElementContentAsString();
}
public void WriteXml(XmlWriter writer)
{
writer.WriteStartElement(Type);
writer.WriteString(Number);
writer.WriteEndElement();
}
}

c# XML Serializing with derived classes using different namespace

I am trying to read a xml file and deserialize it to my InsertSensorType class and than read VALUE1,VALUE2 .. .I tried to add XmlElement name to my InsertionMetadata attribute in InsertSensorTypeMetadata class, but it doesn't work.I have searched and found solutions but they were not valid for this problem. Can anyone please tell me what i am doing wrong.
Xml File :
<?xml version="1.0" encoding="utf-8" ?>
<swes:InsertSensor
xmlns:swes="http://www.opengis.net/swes/2.0"
xmlns:sos="http://www.opengis.net/sos/2.0" >
<swes:metadata>
<sos:SosInsertionMetadata>
<sos:observationType>VALUE1</sos:observationType>
<sos:observationType>VALUE2</sos:observationType>
<sos:featureOfInterestType>VALUE3</sos:featureOfInterestType>
<sos:featureOfInterestType>VALUE4</sos:featureOfInterestType>
</sos:SosInsertionMetadata>
</swes:metadata>
</swes:InsertSensor>
My Classes :
namespace Problem1.Classes
{
[SerializableAttribute()]
[XmlTypeAttribute(Namespace = "http://www.opengis.net/swes/2.0")]
[XmlRootAttribute("InsertSensor", Namespace = "http://www.opengis.net/swes/2.0", IsNullable = false)]
public class InsertSensorType
{
[System.Xml.Serialization.XmlElementAttribute("metadata")]
public InsertSensorTypeMetadata[] metadata { get; set; }
}
[SerializableAttribute()]
[XmlTypeAttribute(AnonymousType = true, Namespace = "http://www.opengis.net/swes/2.0")]
public class InsertSensorTypeMetadata
{
[XmlElement(ElementName = "SosInsertionMetadata", Type = typeof(SosInsertionMetadataType))]
public InsertionMetadataType InsertionMetadata { get; set; }
}
[XmlIncludeAttribute(typeof(SosInsertionMetadataType))]
[SerializableAttribute()]
[XmlTypeAttribute(Namespace = "http://www.opengis.net/swes/2.0")]
public abstract partial class InsertionMetadataType
{
}
[SerializableAttribute()]
[XmlTypeAttribute(Namespace = "http://www.opengis.net/sos/2.0")]
public partial class SosInsertionMetadataType : InsertionMetadataType
{
[System.Xml.Serialization.XmlElementAttribute("observationType", DataType = "anyURI")]
public string[] observationType { get; set; }
[System.Xml.Serialization.XmlElementAttribute("featureOfInterestType", DataType = "anyURI")]
public string[] featureOfInterestType { get; set; }
}
}
And here is my main code :
static void Main(string[] args)
{
XmlElement xmlRequest = null;
XmlDocument doc = new XmlDocument();
doc.Load("request.xml");
xmlRequest = doc.DocumentElement;
executeRequest(xmlRequest);
}
static void executeRequest(XmlElement xmlRequest)
{
InsertSensorType insertSensorRequest = ValidateRequest<InsertSensorType>(xmlRequest);
InsertSensorTypeMetadata[] _InsertSensorTypeMetadata = insertSensorRequest.metadata;
Console.WriteLine("Length of metadata :" + _InsertSensorTypeMetadata.Length);//1
foreach (InsertSensorTypeMetadata istm in _InsertSensorTypeMetadata)
{
SosInsertionMetadataType sos = istm.InsertionMetadata as SosInsertionMetadataType;
//sos is null
}
Console.Read();
}
static T ValidateRequest<T>(XmlElement xmlRequest) where T : class
{
string xml = xmlRequest.OuterXml;
StringReader reader = new StringReader(xml);
XmlSerializer serializer = new XmlSerializer(typeof(T));
//XmlSerializer serializer = new XmlSerializer(typeof(T), new Type[] { typeof(SosInsertionMetadataType) });
T typeInstance = (T)serializer.Deserialize(reader);
return typeInstance;
}
Try this
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using System.IO;
namespace ConsoleApplication1
{
class Program
{
const string FILENAME = #"c:\temp\test.xml";
static void Main(string[] args)
{
InsertSensorType insertSensorType = new InsertSensorType() {
metadata = new Metadata() {
insertSensorTypeMetadata = new InsertSensorTypeMetadata() {
InsertionMetadata = new List<InsertionMetadataType>() {
new ObservationType() { value = "Value1"},
new ObservationType() { value = "Value2"},
new FeatureOfInterestType() { value = "Value3"},
new FeatureOfInterestType() { value = "Value4"}
}
}
}
};
XmlSerializer serializer = new XmlSerializer(typeof(InsertSensorType));
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("swes","http://www.opengis.net/swes/2.0");
ns.Add("sos","http://www.opengis.net/sos/2.0");
StreamWriter writer = new StreamWriter(FILENAME);
serializer.Serialize(writer, insertSensorType, ns);
writer.Flush();
writer.Close();
writer.Dispose();
XmlSerializer xs = new XmlSerializer(typeof(InsertSensorType));
XmlTextReader reader = new XmlTextReader(FILENAME);
InsertSensorType newInsertSensorType = (InsertSensorType)xs.Deserialize(reader);
}
}
[XmlRoot(ElementName = "InsertSensor")]
public class InsertSensorType
{
[XmlElement("metadata")]
public Metadata metadata { get; set; }
}
[XmlRoot("metadata", Namespace = "http://www.opengis.net/swes/2.0")]
public class Metadata
{
[XmlElement("SosInsertionMetadata")]
public InsertSensorTypeMetadata insertSensorTypeMetadata { get; set; }
}
[XmlRoot("SosInsertionMetadata", Namespace = "http://www.opengis.net/sos/2.0")]
public class InsertSensorTypeMetadata
{
[XmlElement()]
public List<InsertionMetadataType> InsertionMetadata { get; set; }
}
[XmlInclude(typeof(ObservationType))]
[XmlInclude(typeof(FeatureOfInterestType))]
[Serializable]
[XmlRoot(Namespace = "http://www.opengis.net/sos/2.0")]
public class InsertionMetadataType
{
public string value { get; set; }
}
[Serializable]
[XmlRoot(ElementName = "observationType", Namespace = "http://www.opengis.net/sos/2.0")]
public class ObservationType : InsertionMetadataType
{
}
[Serializable]
[XmlRoot(ElementName = "featureOfInterestType", Namespace = "http://www.opengis.net/sos/2.0")]
public class FeatureOfInterestType : InsertionMetadataType
{
}
}
​
My solution is that :
added this line to over InsertionMetadata attribute in InsertSensorTypeMetadata class
[System.Xml.Serialization.XmlElement(ElementName = "SosInsertionMetadata", Type = typeof(SosInsertionMetadataType), Namespace = "http://www.opengis.net/sos/2.0")] // added
change my deserialize method
public static T ValidateRequest<T>(XmlElement element) where T : class
{
var serializer = new XmlSerializer(typeof(T));
return (T)serializer.Deserialize(new XmlNodeReader(element));
}
and changed way to print values
static void executeRequest(XmlElement xmlRequest)
{
InsertSensorType insertSensorRequest = ValidateRequest<InsertSensorType>(xmlRequest);
foreach (InsertSensorTypeMetadata istm in insertSensorRequest.metadata)
{
SosInsertionMetadataType sos = (SosInsertionMetadataType)istm.InsertionMetadata;
foreach(string s in sos.featureOfInterestType)
Console.WriteLine(s);
foreach (string s in sos.observationType)
Console.WriteLine(s);
}
}

XmlSerializer not deserializing int array

I have the following type:
[XmlRoot(Namespace = "http://schemas.datacontract.org/2004/07/MyNamespace")]
public class Location
{
public int Id { get; set; }
public string Name { get; set; }
public Collection<int> DataSourceIds { get; set; }
}
I'm serializing a list of Locations to XML, resulting in the following:
<ArrayOfLocation xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/MyNamespace">
<Location>
<DataSourceIds xmlns:d3p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<d3p1:int>1</d3p1:int>
</DataSourceIds>
<Id>2</Id>
<Name>First</Name>
</Location>
<Location>
<DataSourceIds xmlns:d3p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<d3p1:int>1</d3p1:int>
<d3p1:int>2</d3p1:int>
<d3p1:int>3</d3p1:int>
<d3p1:int>4</d3p1:int>
</DataSourceIds>
<Id>1</Id>
<Name>Second</Name>
</Location>
</ArrayOfLocation>
I then try to deserialize this XML as follows:
var rootAttribute = new XmlRootAttribute("ArrayOfLocation")
{
Namespace = "http://schemas.datacontract.org/2004/07/MyNamespace"
};
var serializer = new XmlSerializer(typeof(Location[]), rootAttribute);
using (var xmlReader = XmlReader.Create(new StreamReader(response.GetResponseStream())))
{
locations = (Location[])serializer.Deserialize(xmlReader);
}
This returns a list of Location objects, with every property set correctly... except DataSourceIds, which remains empty. Why isn't XmlSerializer deserializing the array of integers?
Since it was serialized with DataContractSerializer, you can deserialize it like so:
[DataContract(Namespace = "http://schemas.datacontract.org/2004/07/MyNamespace")]
public class Location
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public Collection<int> DataSourceIds { get; set; }
}
And then use it like:
using (var xmlReader = XmlReader.Create(stream))
{
DataContractSerializer serializer = new DataContractSerializer(typeof(Location[]));
var locations = (Location[])serializer.ReadObject(xmlReader);
Debug.WriteLine(DataContractSerializerHelper.GetXml(locations, serializer)); // Debug check on re-serialization, remove when not needed.
}
The XmlRoot declaration is ignored by DataContractSerializer.
Finally, a utility method for re-serializing to an XML string, for debugging purposes:
public static class DataContractSerializerHelper
{
private static MemoryStream GenerateStreamFromString(string value)
{
return new MemoryStream(Encoding.Unicode.GetBytes(value ?? ""));
}
public static string GetXml<T>(T obj, DataContractSerializer serializer) where T : class
{
using (var textWriter = new StringWriter())
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.IndentChars = " "; // For cosmetic purposes.
using (XmlWriter xmlWriter = XmlWriter.Create(textWriter, settings))
{
serializer.WriteObject(xmlWriter, obj);
}
return textWriter.ToString();
}
}
public static string GetXml<T>(T obj) where T : class
{
DataContractSerializer serializer = new DataContractSerializer(typeof(T));
return GetXml(obj, serializer);
}
}

XmlSerializer putting element name inside a property when deserializing

I have an issue with the .NET XML Serializer where I have do different XML Elements with different names that map to the same type. Basically, the objects should be exactly the same, but I want them to have a string or enum or something that identifies which of the three possible element names were used. So here's an example:
<Body>
<MyTypeA>
<Foo>bar</Foo>
</MyTypeA>
<MyTypeB>
<Foo>bar</Foo>
</MyTypeB>
</Body>
Now, for the classes, MyTypeA and MyTypeB will both be the same type. For example:
public class Body {
public MyType MyTypeA { get; set; }
public MyType MyTypeB { get; set; }
}
public class MyType {
public string Foo { get; set; }
[XmlIgnore]
public MyTypeType { get; set; }
}
public enum MyTypeType
{
MyTypeA,
MyTypeB
}
When serializing it works fine, because I can always just ensure one way or another that the enum is set properly before serializing. But when deserializing it is not getting set and I'm not sure there's a way how.
For the record, I unfortunately don't get to set the schema, otherwise I would have built it in such a way that I didn't have this problem.
If i understood your question correctly, this might help you. Just write you XML file path on the 4th line and try it.
namespace ConsoleApplication1
{
class Program
{
//private const string xmlPath = #"C:\Users\Jumast\Desktop\StackOverflowQuestion.xml";
private const string xmlPath, // put the file path here
static Body makeBody()
{
var instance1 = new MyType() { Category = Category.MyTypeA, Foo = "bar" };
var instance2 = new MyType() { Category = Category.MyTypeB, Foo = "bar" };
return new Body(){Instance1 = instance1, Instance2 = instance2};
}
static void serializeBody(Body body, string path)
{
var ser = new DataContractSerializer(body.GetType(), body.GetType().Name, "");
using (var w = XmlWriter.Create(path, new XmlWriterSettings() { Indent = true }))
{
ser.WriteObject(w, body);
}
}
static Body deseerializeBody(string xmlPath)
{
Body deserializedBody;
var ser = new XmlSerializer(typeof(Body));
using (Stream stream = File.OpenRead(xmlPath))
{
deserializedBody = (Body)ser.Deserialize(stream);
}
return deserializedBody;
}
static void writeBodyToConsole(Body body)
{
Console.WriteLine("Instance1: " + body.Instance1);
Console.WriteLine("Instance2: " + body.Instance2);
Console.ReadKey();
}
static void Main(string[] args)
{
serializeBody(makeBody(), xmlPath);
writeBodyToConsole(deseerializeBody(xmlPath));
}
}
public class Body : IXmlSerializable
{
#region Properties
public MyType Instance1 { get; set; }
public MyType Instance2 { get; set; }
#endregion
#region IXmlSerializable
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
reader.ReadStartElement();
Instance1 = new MyType(reader);
Instance2 = new MyType(reader);
reader.ReadEndElement();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
Instance1.WriteXml(writer);
Instance2.WriteXml(writer);
}
#endregion
}
public class MyType : IXmlSerializable
{
#region Fields
private Category _category;
#endregion
#region Constructors
public MyType()
{
_category = Category.nil;
Foo = string.Empty;
}
public MyType(XmlReader reader) { ReadXml(reader);}
#endregion
#region Methods
public override string ToString()
{
var sb = new StringBuilder();
sb.Append(string.Format("Foo = {0}", Foo));
sb.Append(" , ");
sb.Append(string.Format("Category = {0}", Category));
return sb.ToString();
}
#endregion
#region Properties
public string Foo { get; set; }
public Category Category
{
get { return this._category; }
set { this._category = value; }
}
#endregion
#region IXmlSerializable
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
Enum.TryParse(reader.Name, out _category);
reader.Read();
Foo = reader.ReadElementContentAsString("Foo", "");
reader.ReadEndElement();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
writer.WriteStartElement(this.Category.ToString(), "");
writer.WriteElementString("Foo", Foo);
writer.WriteEndElement();
}
#endregion
}
public enum Category
{
MyTypeA,
MyTypeB,
nil
}
}

DataContract Serialization of List<> containing objects

While developing a test case to understand serialization, I've run into what looks like a straighforward problem but am unable to figure out. I want to be able to add multiple objects to a list<> and then serialize that list (in this case I'm using DataContractJsonSerializer). After creating the objects (node1 and node2), I want to add them to a List<> (cn) and serialize it. However, I get an invalid argument error when adding node1 and node2 ("cannot convert from 'JSON_test.Aspirate' to 'JSON_test.CompositeNode'). I believe it is a matter of letting the array know about the base types, but I don't know how to do that, or if in fact that is the issue (still very new to all this).
Thank you.
namespace JSON_test
{
class Program
{
static void Main(string[] args)
{
Aspirate node1 = new Aspirate(25,40);
Dispense node2 = new Dispense(32,50);
ObjectToSerialize cn = new ObjectToSerialize();
cn.CompositeNode.Add (node1);
cn.CompositeNode.Add (node2);
MemoryStream stream1 = new MemoryStream();
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(ObjectToSerialize));
ser.WriteObject(stream1, cn.CompositeNode);
stream1.Position = 0;
StreamReader sr = new StreamReader(stream1);
Console.WriteLine(sr.ReadToEnd());
Console.ReadLine();
}
}
[DataContract]
public class ObjectToSerialize
{
private List<CompositeNode> compNode;
[DataMember]
public List<CompositeNode> CompositeNode
{
get {return this.CompositeNode;}
set { this.compNode = value; }
}
public ObjectToSerialize()
{
}
}
[DataContract]
public class CompositeNode
{
}
[DataContract]
public class Aspirate
{
[DataMember]
public string NodeName = "Aspirate";
[DataMember]
public double ZTravelHt;
[DataMember]
public double IndexHt;
public Aspirate(double ZTravelHt, double IndexHt)
{
this.ZTravelHt = ZTravelHt;
this.IndexHt = IndexHt;
}
}
[DataContract]
public class Dispense
{
[DataMember]
public string NodeName = "Dispense";
[DataMember]
public double ZTravelHt;
[DataMember]
public double IndexHt;
public Dispense(double ZTravelHt, double IndexHt)
{
this.ZTravelHt = ZTravelHt;
this.IndexHt = IndexHt;
}
}
}
UPDATE
namespace JSON_test
{
class Program
{
static void Main(string[] args)
{
Aspirate node1 = new Aspirate(25,40);
Dispense node2 = new Dispense(32,50);
ObjectToSerialize cn = new ObjectToSerialize();
cn.CompositeNode.Add (node1);
cn.CompositeNode.Add (node2);
MemoryStream stream1 = new MemoryStream();
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(ObjectToSerialize),
new Type[] {typeof (Aspirate), typeof (Dispense)});
ser.WriteObject(stream1, cn.CompositeNode);
stream1.Position = 0;
StreamReader sr = new StreamReader(stream1);
Console.WriteLine(sr.ReadToEnd());
Console.ReadLine();
}
}
[DataContract]
[KnownType(typeof(Aspirate))]
[KnownType(typeof(Dispense))]
public class ObjectToSerialize
{
private List<CompositeNode> compNode = new List<CompositeNode>();
[DataMember]
public List<CompositeNode> CompositeNode
{
get {return this.compNode;}
set { this.compNode = value; }
}
public ObjectToSerialize()
{
}
}
[DataContract]
[KnownType(typeof(Aspirate))]
[KnownType(typeof(Dispense))]
public class CompositeNode
{
}
[DataContract]
public class Aspirate : CompositeNode
{
[DataMember]
public string NodeName = "Aspirate";
[DataMember]
public double ZTravelHt;
[DataMember]
public double IndexHt;
public Aspirate(double ZTravelHt, double IndexHt)
{
this.ZTravelHt = ZTravelHt;
this.IndexHt = IndexHt;
}
}
[DataContract]
public class Dispense : CompositeNode
{
[DataMember]
public string NodeName = "Dispense";
[DataMember]
public double ZTravelHt;
[DataMember]
public double IndexHt;
public Dispense(double ZTravelHt, double IndexHt)
{
this.ZTravelHt = ZTravelHt;
this.IndexHt = IndexHt;
}
}
}
You can add a KnownTypeAttribute to the ObjectToSerialize to let the serializer know which types to expect:
[DataContract]
[KnownType(typeof(Aspirate))]
[KnownType(typeof(Dispense))]
public class ObjectToSerialize
{
....
}
I understand that the classes Aspirate and Dispense are derived from CompositeNode? This is not clear from your code example.
You have an error in your code by the way:
get {return this.CompositeNode;}
should be:
get {return this.compNode;}
Update: in resoponse to your question in the comments: you have to intialize the collection inside a constructor of ObjectToSerialize:
public ObjectToSerialize()
{
this.compNode = new List<CompositeNode>();
}
Update 2: The line that is wrong is:
ser.WriteObject(stream1, cn.CompositeNode);
This should be:
ser.WriteObject(stream1, cn);
By the way, you can just write this:
DataContractJsonSerializer ser = new DataContractJsonSerializer(typeof(ObjectToSerialize));
You already defined the known types by using attributes. Adding the known types to the constructor is redundant.

Categories