C# serialize simple object to xml. no namespace no Nulls - c#

I got the following code:
public class Alarm:IDisposable
{
MemoryStream _tmp;
readonly XmlDocument _doc;
private readonly XmlSerializerNamespaces _ns;
private readonly XmlSerializer _x = new XmlSerializer(typeof(Alarm));
public int? ID { get; set; }
public string SourceSystem { get; set; }
public string SensorName { get; set; }
public string ModelName { get; set; }
public int? Severity { get; set; }
public int? Duration { get; set; }
public bool? Status { get; set; }
public DateTime? StartTime { get; set; }
public DateTime? EndTime { get; set; }
public Alarm()
{
_tmp = new MemoryStream();
_doc = new XmlDocument();
_ns = new XmlSerializerNamespaces();
_ns.Add("", "");
}
public string OuterXml()
{
//Add an empty namespace and empty value
_x.Serialize(_tmp, this, _ns);
_tmp.Position = 0;
_doc.Load(_tmp);
return _doc.OuterXml;
}
public void Dispose()
{
if (_tmp!=null)
{
_tmp.Close();
_tmp = null;
}
}
}
I get as output"
<?xml version=\"1.0\"?>
<Alarm><ID>1</ID><SourceSystem>HMer</SourceSystem>
<SensorName>4</SensorName><Severity d2p1:nil=\"true\" xmlns:d2p1=\"http://www.w3.org/2001/XMLSchema-instance\" />
<Duration>500</Duration><Status>true</Status>
<StartTime>2011-07-19T12:29:51.171875+03:00</StartTime>
<EndTime d2p1:nil=\"true\"
xmlns:d2p1=\"http://www.w3.org/2001/XMLSchema-instance\" /></Alarm>
I wanna get:
<Alarm><ID>1</ID><SourceSystem>HMer</SourceSystem><SensorName>4</SensorName>
<Duration>500</Duration><Status>true</Status>
<StartTime>2011-07-19T12:29:51.171875+03:00</StartTime></Alarm>
meaning no xmlns stuff, no tag where value is null.
please assist meh

Add the following properties to your Nullable fields:
[XmlIgnore]
public bool EndTimeSpecified { get { return EndTime.HasValue; } }
This lets XmlSerializer (which is a bit dumb about Nulllable fields) realize that no EndTime has been specified, and, thus, the field does not need to be serialized.
Here's the documentation of this feature:
Another option is to use a special pattern to create a Boolean field recognized by the XmlSerializer, and to apply the XmlIgnoreAttribute to the field. The pattern is created in the form of propertyNameSpecified. For example, if there is a field named "MyFirstName" you would also create a field named "MyFirstNameSpecified" that instructs the XmlSerializer whether to generate the XML element named "MyFirstName".
Here's a related SO question that offers more details and explanations:
How to make a value type nullable with .NET XmlSerializer?
About removing the XML processing instruction (i.e., the <?xml ... ?> part), there's also a SO question about that:
Omitting XML processing instruction when serializing an object

Related

Serialize an element value with its attributes

I have an XML coming in this form:
<run>
<foo status="1">111.9</foo>
<fred status="0">5.5</fred>
</run>
I would like to deserialize this in either of these forms below (I'm undecided, and hoping an answer will help me decide, although I tend to prefer #1, for design aesthetics as much as anything else):
Case # 1
[Serializable]
public class DataValue
{
[XmlAttribute("status")]
public int Status { get; set; }
// I need something here, but what?
public float Value { get; set; }
}
[Serializable]
[XmlRoot("run")]
public class DataBag
{
[XmlElement("foo")]
public DataValue Foo{ get; set; }
[XmlElement("fred")]
public DataValue Fred{ get; set; }
}
When I try this, I get a value of 0 for either member foo or fred.
Case # 2
[Serializable]
[XmlRoot("run")]
public class DataBag2
{
[XmlElement("foo")]
public float Foo{ get; set; }
[XmlElement("foo")]
[XmlAttribute("status")]
public int Foo_status { get; set; }
[XmlElement("fred")]
public float Fred{ get; set; }
[XmlElement("fred")]
[XmlAttribute("status")]
public int Fred_status { get; set; }
}
It compiles but I get an InvalidOperationException while reflecting Foo_status, for which the innermost exception is "For non-array types, you may use the following attributes: XmlAttribute, XmlText, XmlElement, or XmlAnyElement."
What can I do to end up with an actual value in case #1, or no exception (and a valid value and status) for case #2?
The code for the serialization goes like this:
// Case 1
using (var sr = new StreamReader("data.xml"))
{
var xs = new XmlSerializer(typeof(DataBag));
var run = (DataBag)xs.Deserialize(sr);
Console.WriteLine("Got a run: {0}-{1}", run.Fred.Value, run.Fred.Status);
// Issue here is that value is always 0, but status is accurate
}
// case 2
using (var sr = new StreamReader("data.xml"))
{
var xs = new XmlSerializer(typeof(DataBag2));// Exception here
var run = (DataBag2)xs.Deserialize(sr);
Console.WriteLine("Got a run: {0}-{1}", run.Foo, run.Foo_status);
}
Thanks for your attention!
For case 1 you just need to mark it as XMLText:
[XmlText]
public float Value { get; set; }
You want to use [XmlText]:
Indicates to the XmlSerializer that the member must be treated as XML text when the class that contains it is serialized or deserialized.
Thus:
public class DataValue
{
[XmlAttribute("status")]
public int Status { get; set; }
[XmlText]
public float Value { get; set; }
}
Case #2 just won't work as you want. Adding [XmlAttribute("status")] to Foo_status means that Foo_status will be serialized as an attribute of DataBag2, not Foo. Applying [XmlElement("foo")] as well then says it's an element of DataBag2, which is of course in conflict with other attribute.
There's no way with XmlSerializer for an outer container type to specify an attribute to be applied to a nested element.

XML deserialization ignores properties out of alphabetical order

I have small problem - XML deserialization completely ignores items, which are out of alphabetic order. In example object (description in end of question), Birthday node is after FirstName node, and it is ignored and assigned default value after deserialization. Same for any other types and names (I had node CaseId of Guid type after node Patient of PatientInfo type, and after deserialization it had default value).
I'm serializing it in one application, using next code:
public static string SerializeToString(object data)
{
if (data == null) return null;
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("", "");
// what should the XmlWriter do?
var settings = new XmlWriterSettings
{
OmitXmlDeclaration = true,
NewLineChars = ""
};
using (var stringwriter = new System.IO.StringWriter())
{
// Use an XmlWriter to wrap the StringWriter
using (var xmlWriter = XmlWriter.Create(stringwriter, settings))
{
var serializer = new XmlSerializer(data.GetType(), "");
// serialize to the XmlWriter instance
serializer.Serialize(xmlWriter, data, ns);
return stringwriter.ToString();
}
}
}
Such approach was used to get proper result as argument for WebMethod (full problem described here). Results are something like this:
<PatientInfo><FirstName>Foo</FirstName><Birthday>2015-12-19T16:21:48.4009949+01:00</Birthday><RequestedClientID>00000000-0000-0000-0000-000000000000</RequestedClientID>00000000-0000-0000-0000-000000000000</patientId></PatientInfo>
Also I'm deserializing it in another application in simple manner
public static T Deserialize<T>(string xmlText)
{
if (String.IsNullOrEmpty(xmlText)) return default(T);
using (var stringReader = new StringReader(xmlText))
{
var serializer = new XmlSerializer(typeof(T));
return (T)serializer.Deserialize(stringReader);
}
}
Example object:
[XmlRoot("PatientInfo")]
public class PatientInfo
{
[XmlElement("FirstName")]
public string FirstName { get; set; }
[XmlElement("LastName")]
public string LastName { get; set; }
[XmlElement("SSN")]
public string SSN { get; set; }
[XmlElement("Birthday")]
public DateTime? Birthday { get; set; }
[XmlElement("RequestedClientID")]
public Guid RequestedClientID { get; set; }
[XmlElement("patientId")]
public Guid patientId { get; set; }
}
So, I'd like to have answer for one of two questions - 1) How can I adjust my serialization to have all items in alphabetical order? 2) How can I adjust my deserialization, so it won't ignore items out of alphabetical order?
Any help is appreciated.
Update:
Just figured out, that deserialization method I'm using is not actually used at all in my problem, since I'm using serialized info as data with WebMethod, and it is deserialized with some internal mechanism of WCF.
WCF uses DataContractSerializer. This serializer is sensitive to XML element order, see Data Member Order. There's no quick way to disable this, instead you need to replace the serializer with XmlSerializer.
To do this, see Using the XmlSerializer Class, then and apply [XmlSerializerFormat] to your service, for instance:
[ServiceContract]
[XmlSerializerFormat]
public interface IPatientInfoService
{
[OperationContract]
public void ProcessPatientInfo(PatientInfo patient)
{
// Code not shown.
}
}
[XmlRoot("PatientInfo")]
public class PatientInfo
{
[XmlElement("FirstName")]
public string FirstName { get; set; }
[XmlElement("LastName")]
public string LastName { get; set; }
[XmlElement("SSN")]
public string SSN { get; set; }
[XmlElement("Birthday")]
public DateTime? Birthday { get; set; }
[XmlElement("RequestedClientID")]
public Guid RequestedClientID { get; set; }
[XmlElement("patientId")]
public Guid patientId { get; set; }
}

How to load XML Elements using LINQ from XDocument into a class (not using Descendants)

How to load XML Elements using LINQ from XDocument into a c# class. I don't want to use XDocument.Descendants because the settings are not repeated and occur only once in the XML.
This works but I think there must be another way where I don't have to use IEnumerable or use ToArray() and use the first element [0]. Any suggestions?
CODE
public class BackupItem // class needed so LINQ reads XML into an editable DbGrid
{
public bool Backup { get; set; }
public bool IncludeSubDir { get; set; }
public string BackupLabel { get; set; }
public string BackupPath { get; set; }
}
public class BackupSettings
{
public bool IncludeDateStamp { get; set; }
public bool DatePrefix { get; set; }
public bool DateSuffix { get; set; }
public string DateFormat { get; set; }
public bool ZipCompress { get; set; }
public bool ZipPrefix { get; set; }
public bool ZipSuffix { get; set; }
public string ZipText { get; set; }
public bool BackupToSiblingFolder { get; set; }
public string SiblingFolder { get; set; }
public bool BackupToFolder { get; set; }
public bool IncludeFullPath { get; set; }
public string BackupFolder { get; set; }
}
// use a LINQ query to load xml file into datagridview and make datagrid editable (create class with get/set)
var q = from arg in GvXMLDoc.Descendants("BackupItem")
select new BackupItem()
{
Backup = (bool)arg.Element("IncludeDirectory"),
IncludeSubDir = (bool)arg.Element("IncludeSubDirectories"),
BackupLabel = (string)arg.Element("BackupLabel"),
BackupPath = (string)arg.Element("BackupPath")
};
dataGridView1.DataSource = q.ToList();
// load global variable
IEnumerable<BackupSettings> GvBackupSettings = from arg in GvXMLDoc.Element("Backup").Elements("Settings")
select new BackupSettings()
{
IncludeDateStamp = (bool)arg.Element("IncludeDateStamp"),
DatePrefix = (bool)arg.Element("DatePrefix"),
DateSuffix = (bool)arg.Element("DateSuffix"),
DateFormat = (string)arg.Element("DateFormat"),
ZipCompress = (bool)arg.Element("ZipCompress"),
ZipPrefix = (bool)arg.Element("ZipPrefix"),
ZipSuffix = (bool)arg.Element("ZipSuffix"),
ZipText = (string)arg.Element("ZipText"),
BackupToSiblingFolder = (bool)arg.Element("BackupToSiblingFolder"),
SiblingFolder = (string)arg.Element("SiblingFolder"),
BackupToFolder = (bool)arg.Element("BackupToFolder"),
IncludeFullPath = (bool)arg.Element("IncludeFullPath"),
BackupFolder = (string)arg.Element("BackupFolder")
};
I can access the values but it seems a very 'messy way'.
var s = GvBackupSettings.ToArray()[0].DateSuffix;
var t = GvBackupSettings.ToArray()[0].DateFormat;
XML FILE
<?xml version='1.0'?>
<Backup>
<Settings>
<IncludeDateStamp>true</IncludeDateStamp>
<DatePrefix>false</DatePrefix>
<DateSuffix>true</DateSuffix>
<DateFormat>yyyy-MM-dd h.m.s</DateFormat>
<ZipCompress>false</ZipCompress>
<ZipPrefix>false</ZipPrefix>
<ZipSuffix>false</ZipSuffix>
<ZipText></ZipText>
<BackupToSiblingFolder>true</BackupToSiblingFolder>
<SiblingFolder>backups</SiblingFolder>
<BackupToFolder>false</BackupToFolder>
<IncludeFullPath>true</IncludeFullPath>
<BackupFolder>C:\\backup</BackupFolder>
</Settings>
<BackupItem>
<IncludeDirectory>true</IncludeDirectory>
<IncludeSubDirectories>true</IncludeSubDirectories>
<BackupLabel>Backup1</BackupLabel>
<BackupPath>C:\TestFiles\Xml\Samples</BackupPath>
</BackupItem>
<BackupItem>
<IncludeDirectory>true</IncludeDirectory>
<IncludeSubDirectories>false</IncludeSubDirectories>
<BackupLabel>Backup2</BackupLabel>
<BackupPath>C:\TestFiles\Xml\Samples</BackupPath>
</BackupItem>
</Backup>
EDIT 1
I also am trying to deserialize the XML (thanks for idea) so I don't have to cast each item. This does not work.. I don't want to have to run the XSD tool either and have a huge class file...
XmlRootAttribute rootAttribute = new XmlRootAttribute("Backup");
XmlSerializer deserializer = new XmlSerializer((typeof(BackupSettings)), rootAttribute);
GvBackupSettings = (BackupSettings)deserializer.Deserialize(XmlReader.Create(GvXMLFileName));
EDIT 2
Ended up serializing and deserializing xml as suggested below using stardard XSD.exe tool to generate the c# class. Especially as I had to read and write to same xml file. Note: Make sure you verify/modify the generated xsd file first.
You don't need a query if you just need the first element:
XElement settings = GvXMLDoc.Element("Backup").Element("Settings");
BackupSettings GvBackupSettings = new BackupSettings
{
IncludeDateStamp = (bool)settings.Element("IncludeDateStamp"),
DatePrefix = (bool)settings.Element("DatePrefix"),
...
};
var s = GvBackupSettings.DateSuffix;
var t = GvBackupSettings.DateFormat;

Controlling XML Serialization of a List<> derived class

I'm converting a Dictionary object into a List<> derived class by means of the following two declarations:
[Serializable]
public class LogItem
{
public string Name { get; set; }
public string Value { get; set; }
public LogItem(string key, string value)
{
Name = key; Value = value;
}
public LogItem() { }
}
public class SerializableDictionary : List<LogItem>
{
public SerializableDictionary(Dictionary<string, string> table)
{
IDictionaryEnumerator index = table.GetEnumerator();
while (index.MoveNext())
{
Put(index.Key.ToString(), index.Value.ToString());
}
}
private void Put(string key, string value)
{
base.Add(new LogItem(key, value));
}
}
I intend to serialize SerializableDictionary by means of the following code:
SerializableDictionary log = new SerializableDictionary(contents);
using (StringWriter xmlText = new StringWriter())
{
XmlSerializer xmlFormat =
new XmlSerializer(typeof(SerializableDictionary), new XmlRootAttribute("Log"));
xmlFormat.Serialize(xmlText, log);
}
Works fine, but I'm unable to change the XML formatting.
This XML document is intended to be sent to a xml database field and is not meant to be deserialized.
I'd rather have both Name and Value
formatted as attributes of the
LogItem element.
However any attempts at using XMLAttribute have resulted in Reflection or compilation errors. I'm at loss as to what I can do to achieve this requirement. Could someone please help?
you can annotate them with XmlAttribute:
[Serializable]
public class LogItem
{
[XmlAttribute]
public string Name { get; set; }
[XmlAttribute]
public string Value { get; set; }
public LogItem(string key, string value)
{
Name = key; Value = value;
}
public LogItem() { }
}
This worked fine for me and produced the following XML (based on sample input):
<?xml version="1.0" encoding="utf-16"?>
<Log xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<LogItem Name="foo" Value="bar" />
<LogItem Name="asdf" Value="bcxcvxc" />
</Log>
There must be something else going on besides what you are showing. I am able to compile and execute the above code both with and without the XmlAttribute attribute applied to the Name and Value properties.
[XmlAttribute]
public string Name { get; set; }
[XmlAttribute]
public string Value { get; set; }

Deserialization Error: The XML element 'name' from namespace '' is already present in the current scope

This is my first time using XML Serialization and this is driving me absolutely nuts after 2 days of trying to troubleshoot this.
I get this error when the deserialization kicks in:
The XML element 'name' from namespace '' is already present in the current scope. Use XML attributes to specify another XML name or namespace for the element.
The error happens on this line in my code:
Album album = (Album)serializer.Deserialize(reader);
I not sure why. There is no dup "name" node so I just don't get it. This is an XML doc received from an HttpWebResponse from a 3rd party REST API.
Here's the complete code:
My Album Class (the type I'm Deserializing to):
public class Album
{
#region Constructors
public Album()
{
}
#endregion
#region ElementConstants
public static class ElementConstants
{
public const string aID = "aid";
public const string Owner = "owner";
public const string AlbumName = "name";
public const string CoverPhotoID = "cover_pid";
public const string CreateDate = "created";
public const string LastModifiedDate = "modified";
public const string Description = "description";
public const string Location = "location";
public const string AlbumURL = "link";
public const string Size = "size";
public const string Visible = "visible";
}
#endregion ElementConstants
#region Public Properties
[XmlArray(ElementName = "photos_GetAlbums_response")]
[XmlArrayItem( "album" )]
public Album[] Albums { get; set; }
[XmlElement (ElementName = ElementConstants.AlbumName, DataType = "string")]
public string AlbumID { get; set; }
[XmlElement(ElementName = ElementConstants.aID, DataType = "int")]
public Int32 CoverPhotoID { get; set; }
[XmlElement(ElementName = ElementConstants.Owner, DataType = "string")]
public string Owner { get; set; }
[XmlElement(ElementName = ElementConstants.AlbumName, DataType = "string")]
public string AlbumName { get; set; }
[XmlElement(ElementName = ElementConstants.aID, DataType = "DateTime")]
public DateTime CreateDate { get; set; }
[XmlElement(ElementName = ElementConstants.LastModifiedDate, DataType = "DateTime")]
public DateTime LastModifiedDate { get; set; }
[XmlElement(ElementName = ElementConstants.Description, DataType = "string")]
public string Description { get; set; }
[XmlElement(ElementName = ElementConstants.Location, DataType = "string")]
public string Location { get; set; }
[XmlElement(ElementName = ElementConstants.AlbumURL, DataType = "string")]
public string Link { get; set; }
[XmlElement(ElementName = ElementConstants.Size, DataType = "size")]
public string Size { get; set; }
[XmlElement(ElementName = ElementConstants.Visible, DataType = "string")]
public string Visible { get; set; }
#endregion
}
My Serializer Class:
public class Serializer
{
public static Album CreateAlbumFromXMLDoc(XmlDocument doc)
{
// Create an instance of a serializer
var serializer = new XmlSerializer(typeof(Album));
var reader = new StringReader(doc.ToString());
// Deserialize the Xml Object and cast to type Album
Album album = (Album)serializer.Deserialize(reader);
return album;
}
}
The XML that I am trying to Deserialized (copied from the Xml Doc object being passed into the CreateAlbumFromXMLDoc method when debugging in VS):
<?xml version="1.0" encoding="UTF-8"?>
<photos_GetAlbums_response xsi:schemaLocation="http://api.example.com/1.0/ http://api.example.com/1.0/xxx.xsd" list="true">
<album>
<aid>3231990241086938677</aid>
<cover_pid>7031990241087042549</cover_pid>
<owner>1337262814</owner>
<name>LA</name>
<created>1233469624</created>
<modified>1233469942</modified>
<description>trip to LA</description>
<location>CA</location>
<link>http://www.example.com/album.php?aid=7333&id=1337262814</link>
<size>48</size>
<visible>friends</visible>
</album>
<album>
<aid>7031990241086936240</aid>
<cover_pid>7031990241087005994</cover_pid>
<owner>1337262814</owner>
<name>Wall Photos</name>
<created>1230437805</created>
<modified>1233460690</modified>
<description/>
<location/>
<link>http://www.example.com/album.php?aid=3296&id=1337262814</link>
<size>34</size>
<visible>everyone</visible>
</album>
<album>
<aid>7031990241086937544</aid>
<cover_pid>7031990241087026027</cover_pid>
<owner>1337262814</owner>
<name>Mobile Uploads</name>
<created>1231984989</created>
<modified>1233460349</modified>
<description/>
<location/>
<link>http://www.example.com/album.php?aid=6300&id=1337262814</link>
<size>3</size>
<visible>friends</visible>
</album>
<album>
<aid>7031990241086936188</aid>
<cover_pid>7031990241087005114</cover_pid>
<owner>1337262814</owner>
<name>Christmas 2008</name>
<created>1230361978</created>
<modified>1230362306</modified>
<description>My Album</description>
<location/>
<link>http://www.example.com/album.php?aid=5234&id=1337262814</link>
<size>50</size>
<visible>friends</visible>
</album>
<album>
<aid>7031990241086935881</aid>
<cover_pid>7031990241087001093</cover_pid>
<owner>1637262814</owner>
<name>Hock</name>
<created>1229889219</created>
<modified>1229889235</modified>
<description>Misc Pics</description>
<location/>
<link>http://www.example.com/album.php?aid=4937&id=1637262814</link>
<size>1</size>
<visible>friends-of-friends</visible>
</album>
<album>
<aid>7031990241086935541</aid>
<cover_pid>7031990241086996817</cover_pid>
<owner>1637262814</owner>
<name>Test Album 2 (for work)</name>
<created>1229460455</created>
<modified>1229460475</modified>
<description>this is a test album</description>
<location/>
<link>http://www.example.com/album.php?aid=4547&id=1637262814</link>
<size>1</size>
<visible>everyone</visible>
</album>
<album>
<aid>7031990241086935537</aid>
<cover_pid>7031990241086996795</cover_pid>
<owner>1637262814</owner>
<name>Test Album (for work)</name>
<created>1229459168</created>
<modified>1229459185</modified>
<description>Testing for work</description>
<location/>
<link>http://www.example.com/album.php?aid=4493&id=1637262814</link>
<size>1</size>
<visible>friends</visible>
</album>
</photos_GetAlbums_response>
A side note: Just for the hell of it, I paste that XML into XML Notepad 2007, it tells me:
Your XML document contains no xml-stylesheet processing instruction. To provide an XSLT transform, add the following to the top of your file and edit the href attribute accordingly:
I don't think that really means it's malformed or anything but just something to note.
So..
My ultimate goal is to get pass this damn error obviously and get an array of albums back using my code above once I can get past the error. I also want to make sure my code is correct in trying to retrieve that arrray back of albums using my Album[] property in my Album class or anything else I might be missing here. I think it's pretty close and should work but it's not.
Follow-up. I've been pulling my hair out since then.
Here's the latest. I did not use some things for now (from Marc) like the Enum, etc. I might change that later. I also pulled out the datetime stuff as it just looked wierd and I did not get errors on that anway without...at least yet. The main problem now is still my damn XML.
It's still appearing to have problems with the format I guess? Unless it's covering up another problem, no clue. This is driving me fing crazy.
I now get this error when the deserialization kicks in:
Data at the root level is invalid. Line 1, position 1.
The error happens on this line in my code: GetAlbumsResponse album = (GetAlbumsResponse)serializer.Deserialize(reader);
How I get the response into an XmL doc:
public static XmlDocument GetResponseXmlDocument(HttpWebResponse response)
{
Stream dataStream = null; // stream from WebResponse
XmlDocument doc = new XmlDocument();
if (doc == null)
{
throw new NullReferenceException("The web reponse was null");
}
// Get the response stream so we can read the body of the response
dataStream = response.GetResponseStream();
// Open the stream using a StreamReader for easy access
StreamReader reader = new StreamReader(dataStream);
// Load response into string variable so that we can then load into an XML doc
string responseString = reader.ReadToEnd();
// Create an XML document & load it with the response data
doc.LoadXml(responseString);
// Final XML document that represents the response
return doc;
}
My Album Class & Root Level Class (thanks to help from Marc..I get it now):
namespace xxx.Entities
{
[Serializable, XmlRoot("photos_GetAlbums_response")]
public class GetAlbumsResponse
{
[XmlElement("album")]
public List<Album> Albums { get; set; }
[XmlAttribute("list")]
public bool IsList { get; set; }
}
public class Album
{
#region Constructors
public Album()
{
}
#endregion
#region ElementConstants
/// <summary>
/// Constants Class to eliminate use of Magic Strings (hard coded strings)
/// </summary>
public static class ElementConstants
{
public const string aID = "aid";
public const string Owner = "owner";
public const string AlbumName = "name";
public const string CoverPhotoID = "cover_pid";
public const string CreateDate = "created";
public const string LastModifiedDate = "modified";
public const string Description = "description";
public const string Location = "location";
public const string AlbumURL = "link";
public const string Size = "size";
public const string Visible = "visible";
}
#endregion ElementConstants
#region Public Properties
[XmlElement (ElementName = ElementConstants.aID, DataType = "string")]
public string AlbumID { get; set; }
[XmlElement(ElementName = ElementConstants.CoverPhotoID, DataType = "int")]
public Int32 CoverPhotoID { get; set; }
[XmlElement(ElementName = ElementConstants.Owner, DataType = "string")]
public string Owner { get; set; }
[XmlElement(ElementName = ElementConstants.AlbumName, DataType = "string")]
public string AlbumName { get; set; }
public string Created { get; set; }
public DateTime Modified { get; set; }
[XmlElement(ElementName = ElementConstants.Description, DataType = "string")]
public string Description { get; set; }
[XmlElement(ElementName = ElementConstants.Location, DataType = "string")]
public string Location { get; set; }
[XmlElement(ElementName = ElementConstants.AlbumURL, DataType = "string")]
public string Link { get; set; }
public string Size { get; set; }
[XmlElement(ElementName = ElementConstants.Visible, DataType = "string")]
public string Visible { get; set; }
#endregion
}
}
My Serializer Class:
namespace xxx.Utilities
{
public class Serializer
{
public static List<Album> CreateAlbumFromXMLDoc(XmlDocument doc)
{
// Create an instance of a serializer
var serializer = new XmlSerializer(typeof(Album));
var reader = new StringReader(doc.ToString());
// Deserialize the Xml Object and cast to type Album
GetAlbumsResponse album = (GetAlbumsResponse)serializer.Deserialize(reader);
return album.Albums;
}
}
}
The true XML incoming, that I am trying to Deserialize (yes it does have xmlns):
<?xml version="1.0" encoding="UTF-8"?>
<photos_GetAlbums_response xmlns="http://api.example.com/1.0/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://api.example.com/1.0/ http://api.example.com/1.0/xxx.xsd" list="true">
<album>
<aid>7321990241086938677</aid>
<cover_pid>7031990241087042549</cover_pid>
<owner>1124262814</owner>
<name>Album Test 1</name>
<created>1233469624</created>
<modified>1233469942</modified>
<description>Our trip</description>
<location>CA</location>
<link>http://www.example.com/album.php?aid=7733&id=1124262814</link>
<size>48</size>
<visible>friends</visible>
</album>
<album>
<aid>231990241086936240</aid>
<cover_pid>7042330241087005994</cover_pid>
<owner>1124262814</owner>
<name>Album Test 2</name>
<created>1230437805</created>
<modified>1233460690</modified>
<description />
<location />
<link>http://www.example.com/album.php?aid=5296&id=1124262814</link>
<size>34</size>
<visible>everyone</visible>
</album>
<album>
<aid>70319423341086937544</aid>
<cover_pid>7032390241087026027</cover_pid>
<owner>1124262814</owner>
<name>Album Test 3</name>
<created>1231984989</created>
<modified>1233460349</modified>
<description />
<location />
<link>http://www.example.com/album.php?aid=6600&id=1124262814</link>
<size>3</size>
<visible>friends</visible>
</album>
</photos_GetAlbums_response>
Personally, I wouldn't use constants here - they make it hard to spot errors (and since you probably aren't re-using them, don't add much). For example:
[XmlElement (ElementName = ElementConstants.AlbumName, DataType = "string")]
public string AlbumID { get; set; }
...
[XmlElement(ElementName = ElementConstants.AlbumName, DataType = "string")]
public string AlbumName { get; set; }
Looks suspect to me...
An easier approach is to write the xml you want to a file (foo.xml, say) and use:
xsd foo.xml
xsd foo.xsd /classes
Then look at foo.cs.
Here we go... note the xml was invalid (& should be &; use of undeclared xsi namespace-alias). Note also that I added an enum for the visibility, added handling for converting the long to DateTime, and added the wrapper type:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
static class Program
{
const string xml = #"<?xml version=""1.0"" encoding=""UTF-8""?>
<photos_GetAlbums_response
xmlns:xsi=""http://www.w3.org/2001/XMLSchema-instance""
xsi:schemaLocation=""http://api.example.com/1.0/ http://api.example.com/1.0/xxx.xsd""
list=""true"">
<album>
<aid>3231990241086938677</aid>
<cover_pid>7031990241087042549</cover_pid>
<owner>1337262814</owner>
<name>LA</name>
<created>1233469624</created>
<modified>1233469942</modified>
<description>trip to LA</description>
<location>CA</location>
<link>http://www.example.com/album.php?aid=7333&id=1337262814</link>
<size>48</size>
<visible>friends</visible>
</album>
<album>
<aid>7031990241086936240</aid>
<cover_pid>7031990241087005994</cover_pid>
<owner>1337262814</owner>
<name>Wall Photos</name>
<created>1230437805</created>
<modified>1233460690</modified>
<description/>
<location/>
<link>http://www.example.com/album.php?aid=3296&id=1337262814</link>
<size>34</size>
<visible>everyone</visible>
</album>
<album>
<aid>7031990241086937544</aid>
<cover_pid>7031990241087026027</cover_pid>
<owner>1337262814</owner>
<name>Mobile Uploads</name>
<created>1231984989</created>
<modified>1233460349</modified>
<description/>
<location/>
<link>http://www.example.com/album.php?aid=6300&id=1337262814</link>
<size>3</size>
<visible>friends</visible>
</album>
<album>
<aid>7031990241086936188</aid>
<cover_pid>7031990241087005114</cover_pid>
<owner>1337262814</owner>
<name>Christmas 2008</name>
<created>1230361978</created>
<modified>1230362306</modified>
<description>My Album</description>
<location/>
<link>http://www.example.com/album.php?aid=5234&id=1337262814</link>
<size>50</size>
<visible>friends</visible>
</album>
<album>
<aid>7031990241086935881</aid>
<cover_pid>7031990241087001093</cover_pid>
<owner>1637262814</owner>
<name>Hock</name>
<created>1229889219</created>
<modified>1229889235</modified>
<description>Misc Pics</description>
<location/>
<link>http://www.example.com/album.php?aid=4937&id=1637262814</link>
<size>1</size>
<visible>friends-of-friends</visible>
</album>
<album>
<aid>7031990241086935541</aid>
<cover_pid>7031990241086996817</cover_pid>
<owner>1637262814</owner>
<name>Test Album 2 (for work)</name>
<created>1229460455</created>
<modified>1229460475</modified>
<description>this is a test album</description>
<location/>
<link>http://www.example.com/album.php?aid=4547&id=1637262814</link>
<size>1</size>
<visible>everyone</visible>
</album>
<album>
<aid>7031990241086935537</aid>
<cover_pid>7031990241086996795</cover_pid>
<owner>1637262814</owner>
<name>Test Album (for work)</name>
<created>1229459168</created>
<modified>1229459185</modified>
<description>Testing for work</description>
<location/>
<link>http://www.example.com/album.php?aid=4493&id=1637262814</link>
<size>1</size>
<visible>friends</visible>
</album>
</photos_GetAlbums_response>";
static void Main()
{
XmlSerializer ser = new XmlSerializer(typeof(GetAlbumsResponse));
GetAlbumsResponse response;
using (StringReader reader = new StringReader(xml))
{
response = (GetAlbumsResponse)ser.Deserialize(reader);
}
}
}
[Serializable, XmlRoot("photos_GetAlbums_response")]
public class GetAlbumsResponse
{
[XmlElement("album")]
public List<Album> Albums {get;set;}
[XmlAttribute("list")]
public bool IsList { get; set; }
}
public enum AlbumVisibility
{
[XmlEnum("")]
None,
[XmlEnum("friends")]
Friends,
[XmlEnum("friends-of-friends")]
FriendsOfFriends,
[XmlEnum("everyone")]
Everyone
}
[Serializable]
public class Album
{
static readonly DateTime epoch = new DateTime(1970, 1, 1);
static long SerializeDateTime(DateTime value)
{
return (long)((value - epoch).TotalSeconds);
}
static DateTime DeserializeDateTime(long value)
{
return epoch.AddSeconds(value);
}
[XmlElement("aid")]
public long AlbumID { get; set; }
[XmlElement("cover_pid")]
public long CoverPhotoID { get; set; }
[XmlElement("owner")]
public long Owner { get; set; }
[XmlElement("name")]
public string AlbumName { get; set; }
[XmlIgnore]
public DateTime CreateDate { get; set; }
[XmlElement("created"), Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public long CreateDateInt64 {
get {return SerializeDateTime(CreateDate);}
set {CreateDate = DeserializeDateTime(value);}
}
[XmlIgnore]
public DateTime LastModifiedDate { get; set; }
[XmlElement("modified"), Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public long LastModifiedDateInt64
{
get { return SerializeDateTime(LastModifiedDate); }
set { LastModifiedDate = DeserializeDateTime(value); }
}
[XmlElement("description")]
public string Description { get; set; }
[XmlElement("location")]
public string Location { get; set; }
[XmlElement("link")]
public string Link { get; set; }
[XmlElement("size")]
public int Size { get; set; }
[XmlElement("visible")]
public AlbumVisibility Visibility { get; set; }
}
(08 Feb) First, treating xml as a string (for reading) isn't going to cause errors.
The problem is the namespace (the xmlns without the xsi); this wasn't in the earlier xml, so I couldn't include it... basically, you need to tell the serializer about it:
[Serializable, XmlRoot("photos_GetAlbums_response",
Namespace="http://api.example.com/1.0/")]
public class GetAlbumsResponse { /* code as before */ }
[Serializable, XmlType(Namespace="http://api.example.com/1.0/")]
public class Album { /* code as before */ }
On this occasion, a constant for the namespace would make sense (since you are re-using it).
If the xml you are showing is accurate, then the links are still corrupt, though... but maybe this is just copy/paste (i.e. don't apply this change until you know it errors...): you need & (not &). Suggest some "Replace"... at the crudest level:
string fixedXml = xml.Replace("&", "&");
(although something more precise might be better - perhaps a regex)
Note that with the different data I also had to make some of the data strings (rather than long):
[XmlElement("aid")]
public string AlbumID { get; set; }
[XmlElement("cover_pid")]
public string CoverPhotoID { get; set; }
[XmlElement("owner")]
public string Owner { get; set; }
With these changes (and mostly my original code) it works.
Of course, by this point you should be thinking "I wish I'd used xsd".
Yes - album is definitely not the root node in your XML.
What I would recommend you do is create a GetAlbumsResponse class which contains a list of albums, and move your deserialize code to the wrapper class.
Basically, remove the root element from your Album class definition, and :
[XmlRoot (ElementName="GetAlbums_response")]
public class GetAlbumsResponse
{
#region Constructors
public GetAlbumsResponse()
{
}
#endregion
[XmlArray(ElementName="album")]
public List<Album> Albums{get;set;}
... deserialization code...
}
Well,
there is a link from Microsoft targeting your problem
The Xml that would work for your current code is something like this:
<Album><photos_GetAlbums_response>
<Album>
<photos_GetAlbums_response>
<Album>
<photos_GetAlbums_response> ....
A response, which has an array of Albums, where each Album has a response which is an Array of Albums...etc.
Anyway, I already helped you in your other question, and even went to the trouble of creating a full working code sample. Why did you create another question for the same problem ?
Use System.Xml.XmlDocument to parse the input. It shouldn't take more than an hour to write the code to extract the data yourself.
Ok - I coded up an example. I took a look at the Facebook API, now here is a FULL working example.
Try this:
[XmlRoot("photos_getAlbums_response", Namespace="http://api.facebook.com/1.0/")]
public class GetAlbumsResponse
{
public GetAlbumsResponse()
{
}
[XmlElement("album")]
public List<Album> Albums { get; set; }
}
public class Album
{
[XmlElement("aid")]
public long Aid{get;set;}
[XmlElement("cover_pid")]
public long CoverPid{get;set;}
[XmlElement("owner")]
public long Owner{get;set;}
[XmlElement("name")]
public string Name{get;set;}
[XmlElement("created")]
public long Created{get;set;}
[XmlElement("modified")]
public long Modified{get;set;}
[XmlElement("description")]
public string Description{get;set;}
[XmlElement("location")]
public string Location{get;set;}
[XmlElement("link")]
public string Link{get;set;}
[XmlElement("size")]
public int Size{get;set;}
[XmlElement("visible")]
public string Visible{get;set;}
public Album()
{}
}
class XmlUtils
{
public static T DeserializeFromXml<T>(string xml)
{
T result;
XmlSerializer ser = new XmlSerializer(typeof(T));
using (TextReader tr = new StringReader(xml))
{
result = (T)ser.Deserialize(tr);
}
return result;
}
}
Now.. with an xml photos_getAlbums_response from the Facebook API,
You can deserialize like this:
GetAlbumsResponse response = XmlUtils.DeserializeFromXml<GetAlbumsResponse>(xmlResponseString);
This is a really old thread but I just faced the same issue my self while tried to serialize two different list types in the same class under the same XmlArray name, something like
<Root>
<ArrayNode>
<SubnodeType1>...</SubnodeType1>
<SubnodeType1>...</SubnodeType1>
</ArrayNode>
</Root>
Or
<Root>
<ArrayNode>
<SubnodeType2>...</SubnodeType2>
<SubnodeType2>...</SubnodeType2>
</ArrayNode>
</Root>
What has worked for me was to use a class decoration like:
[XmlRoot(Namespace = "", ElementName = "Root")]
public class Root
{
[XmlArray(ElementName = "ArrayNode", Namespace = "", IsNullable = false, Order = 1)]
[XmlArrayItem("SubnodeType1")]
public List<SubnodeType1> SubnodeType1 { get; set; }
[XmlArray(ElementName = "ArrayNode", Namespace = "", IsNullable = false, Order = 2)]
[XmlArrayItem("SubnodeType2")]
public List<SubnodeType2> SubnodeType2 { get; set; }
}
Hope it help someone else :)

Categories