C# XML Serializable Collection - c#

I got the below XML (It is just a part of a big XML where I have my problem) that I am trying to make a Serializable class to read the same.
<BANKTRANLIST>
<DTSTART>20051001</DTSTART>
<DTEND>20051028</DTEND>
<STMTTRN> <!-- This element can repeat any number of times -->
<TRNTYPE>CHECK</TRNTYPE>
<DTPOSTED>20051004</DTPOSTED>
<TRNAMT>-200.00</TRNAMT>
</STMTTRN>
<STMTTRN>
<TRNTYPE>ATM</TRNTYPE>
<DTPOSTED>20051020</DTPOSTED>
<TRNAMT>-300.00</TRNAMT>
</STMTTRN>
</BANKTRANLIST>
My C# Implementation
[Serializable]
[XmlRoot("BANKTRANLIST", Namespace = "http://bank.net", IsNullable = false)]
public class BankTransactionList
{
public BankTransactionList()
{
this.StatementTransactions = new List<StatementTransaction>();
}
[XmlElement("DTSTART")]
public string StartDate { get; set; }
[XmlElement("DTEND")]
public string EndDate { get; set; }
[XmlArray("STMTTRN")]
[XmlArrayItem("STMTTRN")]
public List<StatementTransaction> StatementTransactions { get; set; }
}
[Serializable]
[XmlRoot("STMTTRN", Namespace = "http://bank.net", IsNullable = false)]
public class StatementTransaction
{
// TransactionType : ENUM
[XmlElement("TRNTYPE")]
public TransactionType TransactionType { get; set; }
[XmlElement("DTPOSTED")]
public string DatePosted { get; set; }
[XmlElement("TRNAMT")]
public double TransactionAmount { get; set; }
}
My problem is element wrapped again in element which results to get the below output
...
<STMTTRN> <!-- This does not match my Original XML -->
<STMTTRN>
<TRNTYPE>CHECK</TRNTYPE>
<DTPOSTED>20051004</DTPOSTED>
<TRNAMT>-200.00</TRNAMT>
</STMTTRN>
<STMTTRN>
<TRNTYPE>ATM</TRNTYPE>
<DTPOSTED>20051020</DTPOSTED>
<TRNAMT>-300.00</TRNAMT>
</STMTTRN>
</STMTTRN>
Note: Removing [XmlArray("STMTTRN")] tag from List property will not resolve this, instead it will be
If any one can correct me or give me a better solution would be great !!

Should be [XmlElement] if you want an element per item without a wrapper element:
[XmlElement("STMTTRN")]
public List<StatementTransaction> StatementTransactions { get; set; }

I would add that in order to serialize the collection you will need to have it something like this:
[Serializable]
[XmlRoot("BANKTRANLIST", Namespace = "http://bank.net", IsNullable = false)]
public class BankTransactionList
{
public BankTransactionList()
{
StatementTransactions = new List<StatementTransaction>()
{
new StatementTransaction()
};
}
[XmlElement("DTSTART")]
public string StartDate { get; set; }
[XmlElement("DTEND")]
public string EndDate { get; set; }
[XmlElement("STMTTRN")]
public List<StatementTransaction> StatementTransactions { get; set; }
}
Otherwise, without initializing the StatementTransaction object, the list will not be serialized.

Related

XML Deserialization Error ReadElementContentAs()

I am working on deserializing the following XML document into a C# class:
<Station>
<station_id>KMSP</station_id>
<wmo_id>72658</wmo_id>
<latitude>44.88</latitude>
<longitude>-93.23</longitude>
<elevation_m>255.0</elevation_m>
<site>MINNEAPOLIS</site>
<state>MN</state>
<country>US</country>
<site_type>
<METAR />
<TAF />
</site_type>
</Station>
This is the class that it is being deserialized into:
public class Station
{
[XmlElement(ElementName ="station_id")]
public string StationId { get; set; }
[XmlElement(ElementName = "wmo_id")]
public string WmoId { get; set; }
[XmlElement(ElementName ="latitude")]
public double Latitude { get; set; }
[XmlElement(ElementName = "longtitude")]
public double Longtitude { get; set; }
[XmlElement(ElementName="site")]
public string Site { get; set; }
[XmlElement(ElementName ="state")]
public string State { get; set; }
[XmlElement(ElementName ="country")]
public string Country { get; set; }
[XmlElement(ElementName = "site_type")]
public string[] SiteType { get; set; }
}
Everything seems to be deserializng correctly, except for the <site_type> tag. The contents of this tag will vary from Station to Station, but will always contain one or more items of a specific value - essentially from an enum. These tags are always self closing and do not ever contain attributes. They are strictly in the <METAR /> format. When I attempt to deserialize this XML, I get a ReadElementContentAs() error, and I am 99% sure it's that tag that is causing the issue. Would anyone happen to know where I went wrong on this, or have any advice on how to deserialize that kind of element?
Thank you!
Taking your comment on this answer into account
For clarifcation, these type tags in the SiteType will never contain any data like the other tags. If the tag is present, it means that this weather station produces that type of report (METAR, TAF, SIGMENTS, etc)
That SiteType isn't an array of strings, but an array of types, with a different one per report type. All share the same base class.
public abstract class SiteType
{ }
public class Metar : SiteType
{ }
public class Taf : SiteType
{ }
That SiteType property then looks like below
[XmlArray(ElementName = "site_type")]
[XmlArrayItem(typeof(Metar), ElementName ="METAR")]
[XmlArrayItem(typeof(Taf), ElementName = "TAF")]
public SiteType[] SiteType { get; set; }
To check whether a certain report type is supported for a station, you check for the presence of the corresponding type within that SiteType array.
var hasMetar = p.SiteType.Any(o => o.GetType() == typeof(Metar));
You can declare a property as follows:
[XmlAnyElement(Name = "site_type")]
public XmlElement SiteType { get; set; }
After deserialization, there will be a collection of nodes in the property.
foreach(XmlNode node in station.SiteType.ChildNodes)
Console.WriteLine(node.Name);

XML Deserialization error: xxxxx was not expected

I know there are several posts out there with this topic, but I can't seem to figure out what is the problem here. I have serialized and deserialized xml several times, and never had this error.
The exception message is:
There is an error in XML document (1, 2).
With InnerException:
<InvoiceChangeRequest xmlns=''> was not expected.
XML file I want to deserialize:
<ns1:InvoiceChangeRequest xmlns:ns1="http://kmd.dk/fie/external_invoiceDistribution">
<CONTROL_FIELDS>
<STRUCTURID>0000000001</STRUCTURID>
<OPERA>GET</OPERA>
<WIID>000050371220</WIID>
</CONTROL_FIELDS>
<HEADER_IN>
<MANDT>751</MANDT>
<BELNR>1234567890</BELNR>
</HEADER_IN>
<ITEMS>
<ITEM_FIELDS_IN>
<BUZEI>001</BUZEI>
<BUKRS>0020</BUKRS>
</ITEM_FIELDS_IN>
</ITEMS>
</ns1:InvoiceChangeRequest>
Class I'm trying to deserialize to:
[XmlRoot(Namespace = "http://kmd.dk/fie/external_invoiceDistribution", IsNullable = false)]
public class InvoiceChangeRequest
{
[XmlElement("CONTROL_FIELDS")] public ControlFields Styrefelter;
[XmlElement("HEADER_IN")] public HeaderIn HeaderfelterInd;
[XmlElement("ITEMS")] public Items Linjer;
}
public class HeaderIn
{
[XmlElement("MANDT")] public string Kommunenummer;
[XmlElement("BELNR")] public string RegnskabsbilagsNummer;
}
public class Items
{
[XmlElement("ITEM_FIELDS_IN")] public Itemfield[] ItemfelterInd;
}
public class Itemfield
{
[XmlElement("BUZEI")] public string Linjenummer;
[XmlElement("BUKRS")] public string Firmakode;
}
Deserialization code:
XmlSerializer serializer = new XmlSerializer(typeof(InvoiceChangeRequest));
var request = serializer.Deserialize(new StringReader(output)) as InvoiceChangeRequest;
In your XML file your root element is the namespace http://kmd.dk/fie/external_invoiceDistribution with prefix ns1.
The element <CONTROL_FIELDS> isn't because it isn't prefixed. Your serialization class doesn't take this into account though. That means that it expects that <CONTROL_FIELDS> and the other elements are ALSO in the ns1 namespace.
To get the serializer parse the elements correctly add the Namespace to the elements, setting it to an empty string:
[XmlRoot(Namespace = "http://kmd.dk/fie/external_invoiceDistribution", IsNullable = false)]
public class InvoiceChangeRequest
{
[XmlElement("CONTROL_FIELDS", Namespace = "")]
public ControlFields Styrefelter { get; set; }
[XmlElement("HEADER_IN", Namespace = "")]
public HeaderIn HeaderfelterInd { get; set; }
[XmlElement("ITEMS", Namespace = "")]
public Items Linjer { get; set; }
}
This will de-serialize the given XML as intended.
In case of de-serialization issues I often create the classes in memory and then serialize that so I can inspect the resulting XML. That often gives clues on what is missing or being added compared to the input document:
var ms = new MemoryStream();
serializer.Serialize(ms, new InvoiceChangeRequest {
Styrefelter = new ControlFields { Opera="test"}
});
var s = Encoding.UTF8.GetString(ms.ToArray());
And then inspect s for differences.
You can replace 'ns1:' with string.Empty.
Below classes should serialize.
public class Item
{
[XmlElement("BUZEI")]
public string Buzei { get; set; }
[XmlElement("BUKRS")]
public string Bukrs { get; set; }
}
public class Header
{
[XmlElement("MANDT")]
public string Mandt { get; set; }
[XmlElement("BELNR")]
public string Belnr { get; set; }
}
public class ControlFields
{
[XmlElement("STRUCTURID")]
public string StructuredId { get; set; }
[XmlElement("OPERA")]
public string Opera { get; set; }
[XmlElement("WIID")]
public string Wild { get; set; }
}
public class InvoiceChangeRequest
{
[XmlElement("CONTROL_FIELDS")]
public ControlFields ControlFields { get; set; }
[XmlElement("HEADER_IN")]
public Header Header { get; set; }
[XmlArray("ITEMS")]
[XmlArrayItem("ITEM_FIELDS_IN")]
public List<Item> Items { get; set; }
}

De-Serialization of a nested object tree using WCF DataContractSerializer

I have the following SOAP XML Response
<startProcessResponseDDWEBCall xmlns="http://eclipse.org/stardust/models/generated/OmniLinkServices">
<ProcessInstanceOid>13430</ProcessInstanceOid>
<Return>
<DDWEBCallResponseData>
<DDWEBCallOutput xmlns="http://www.infinity.com/bpm/model/OmniLinkServices/DDWEBCallOutput">
<CommonResponse xmlns=""/>
<reportContent xmlns=""><![CDATA[<HTML><BODY><P>The OmniPlus Host Server process DDWEB had the following error: </P><P><TEXT="FF000">E23 TX00087 Textfile not found: TestScript # 000003\\\ </TEXT></P><P>processing terminated</P></BODY></HTML>]]></reportContent>
</DDWEBCallOutput>
</DDWEBCallResponseData>
</Return>
</startProcessResponseDDWEBCall>
This is the SOAP body of a WCF web service call response. I have the following object hierarchy to represent the response
[DataContract(Namespace="http://www.infinity.com/bpm/model/OmniLinkServices/DDWEBCallOutput")]
public class OmniLinkExecuteScriptOutput
{
public string CommonResponse { get; set; }
[DataMember(Name = "reportContent")]
public string ReportContent { get; set; }
}
[DataContract(Namespace = "http://eclipse.org/stardust/models/generated/OmniLinkServices")]
public class OmniLinkExecuteScriptResponseData
{
[DataMember(Name="DDWEBCallOutput")]
public OmniLinkExecuteScriptOutput Output { get; set; }
}
[DataContract(Namespace = "http://eclipse.org/stardust/models/generated/OmniLinkServices")]
public class OmniLinkExecuteScriptReturn
{
[DataMember(Name="DDWEBCallResponseData")]
public OmniLinkExecuteScriptResponseData ReponseData { get; set; }
}
[MessageContract(WrapperName = "startProcessResponseDDWEBCall", WrapperNamespace = "http://eclipse.org/stardust/models/generated/OmniLinkServices", IsWrapped = true)]
public class OmniLinkExecuteScriptResponse
{
[MessageBodyMember(Name = "ProcessInstanceOid", Namespace = "http://eclipse.org/stardust/models/generated/OmniLinkServices")]
public string ProcessInstanceOid { get; set; }
[MessageBodyMember(Name = "Return", Namespace = "http://eclipse.org/stardust/models/generated/OmniLinkServices")]
public OmniLinkExecuteScriptReturn Return { get; set; }
}
The object OmniLinkExecuteScriptResponse is the return type of a method call. Everything is deserialized fine except for the inner most object, the object representing the DDWEBCallOutput node. I don't see any errors in the WCF plumbing, and the value of the Output property on the OmniLinkExecuteScriptResponseData object is always null.
Can anybody tell what I'm doing wrong?
Your problem is that you are applying the [DataContract(Namespace = "...")] attributes at the wrong level in your class hierarchy. This attribute controls the namespace to which all data members of an instance of a type are serialized, and in addition controls the root element namespace if the instance is being serialized as such.
Thus you need to do the following:
// Its members are in the empty namespace
[DataContract(Namespace = "")]
public class OmniLinkExecuteScriptOutput
{
[DataMember]
public string CommonResponse { get; set; }
[DataMember(Name="reportContent")]
public string ReportContent { get; set; }
}
// Its members are in the DDWEBCallOutput namespace
[DataContract(Namespace = "http://www.infinity.com/bpm/model/OmniLinkServices/DDWEBCallOutput")]
public class OmniLinkExecuteScriptResponseData
{
[DataMember(Name = "DDWEBCallOutput")]
public OmniLinkExecuteScriptOutput Output { get; set; }
}
// Its members are in the OmniLinkServices namespace
[DataContract(Namespace = "http://eclipse.org/stardust/models/generated/OmniLinkServices")]
public class OmniLinkExecuteScriptReturn
{
[DataMember(Name = "DDWEBCallResponseData")]
public OmniLinkExecuteScriptResponseData ReponseData { get; set; }
}
OmniLinkExecuteScriptResponse can remain unchanged.

Populate custom List sub class from XML document via LINQ

I have figured out how to populate a custom class from XML data, but I ran into an issue along the way. Things were working perfectly with my existing method of populating data until I was thrown a bit of a curve ball. The new schema I was sent is similar to this:
<ITEM_REPLY>
<TRAN_ID>1320691307345</TRAN_ID>
<REPLY_CODE>0</REPLY_CODE>
<UNIT_PRICE>8.2784</UNIT_PRICE>
<SUP_LOCS>
<SUP_LOC>
<SUP_LOC_ID>001134</SUP_LOC_ID>
<COUNTRY_ID>USA</COUNTRY_ID>
<QTY_AVL>47.000</QTY_AVL>
<ITEM_UOM>EA</ITEM_UOM>
</SUP_LOC>
<SUP_LOC>
<SUP_LOC_ID>006817</SUP_LOC_ID>
<COUNTRY_ID>USA</COUNTRY_ID>
<QTY_AVL>20.000</QTY_AVL>
<ITEM_UOM>EA</ITEM_UOM>
</SUP_LOC>
</SUP_LOCS>
<MESSAGE />
<QTY_BREAKS />
</ITEM_REPLY>
Pretty standard XML schema, problem is I'm not sure how to populate my custom class with it. Here's what I do have:
static void Main(string[] args)
{
var order = ConvertXMLMessage<ItemReply>(request);
}
protected static T ConvertXMLMessage<T>(String xmlData) where T : class, new()
{
var xml = new XmlDocument();
xml.LoadXml(xmlData);
var serializer = new System.Xml.Serialization.XmlSerializer(typeof(T));
using (var xmlReader = new XmlNodeReader(xml.DocumentElement))
{
T work = (T)(serializer.Deserialize(xmlReader));
return work;
}
}
public class ItemReply
{
[XmlElement("ITEM_REPLY")]
public ItemAvlReply ITEM_REPLY { get; set; }
}
public class ItemAvlReply
{
[XmlElement("TRAN_ID")]
public string TRAN_ID { get; set; }
[XmlElement("REPLY_CODE")]
public string REPLY_CODE { get; set; }
[XmlElement("UNIT_PRICE")]
public string UNIT_PRICE { get; set; }
[XmlElement("SUP_LOCS")]
public SupplierLocations SUP_LOCS;
[XmlElement("MESSAGE")]
public string MESSAGE { get; set; }
[XmlElement("QTY_BREAKS")]
public string QTY_BREAKS { get; set; }
}
public class SupplierLocations
{
[XmlElement("SUP_LOC")]
public List<SupplierLocation> SUP_LOC;
}
public class SupplierLocation
{
[XmlElement("SUP_LOC_ID")]
public string SUP_LOC_ID { get; set; }
[XmlElement("COUNTRY_ID")]
public string COUNTRY_ID { get; set; }
[XmlElement("QTY_AVL")]
public string QTY_AVL { get; set; }
[XmlElement("ITEM_UOM")]
public string ITEM_UOM { get; set; }
}
This works perfectly minus the List<Item> part. I'm not overly experienced with LINQ and I'm not sure how to go about declaring a sub array in my class via this statement. I am also open to a different approach from creating the List<Item> part, I'm just not sure where to start otherwise. Is there a better approach for what I'm need to do? Is there an easy solution I am just unaware of in LINQ?
Here's a simple way to do it, assuming the example XML file you provided has typos. I assumed the OrderId has a closing tag, and that the closing tag for Items should be /Items.
Here's the version of the xml I used:
<Order>
<TransactionID>123</TransactionID>
<OrderID>1</OrderID>
<Items Number="2">
<Item>
<ItemName>Test</ItemName>
<Color>Red</Color>
</Item>
<Item>
<ItemName>Test1</ItemName>
<Color>Blue</Color>
</Item>
</Items>
</Order>
Here's the code to read/write the XML: (the xml variable is a String)
var order = ConvertXMLMessage<Order>(xml);
WriteXMLFile<Order>(order, #"test.xml");
Here's the ConvertXMLMessage and WriteXMLFile functions:
protected static T ConvertXMLMessage<T>(String xmlData) where T : class, new()
{
var xml = new XmlDocument();
xml.LoadXml(xmlData);
var serializer = new System.Xml.Serialization.XmlSerializer(typeof(T));
using (var xmlReader = new XmlNodeReader(xml.DocumentElement))
{
T work = (T)(serializer.Deserialize(xmlReader));
return work;
}
}
protected static void WriteXMLFile<T>(T item, String saveLocation) where T : class, new()
{
System.Xml.Serialization.XmlSerializer writer = new System.Xml.Serialization.XmlSerializer(typeof(T));
System.IO.StreamWriter file = new System.IO.StreamWriter(saveLocation);
writer.Serialize(file, item);
file.Close();
}
and here's the class structure:
public class Order
{
[XmlElement("TransactionID")]
public string TransactionId { get; set; }
[XmlElement("OrderID")]
public string OrderId { get; set; }
[XmlElement("Items")]
public ItemsContainer Items;
}
public class ItemsContainer
{
[XmlAttribute("Number")]
public Int32 Number { get; set; }
[XmlElement("Item")]
public List<Item> Items;
}
public class Item
{
[XmlElement("ItemName")]
public string ItemName { get; set; }
[XmlElement("Color")]
public string Color { get; set; }
}
As you'll notice I added some attributes to let the XML parser know how to handle the class when it's converting from/to the XML. I also added another small class called "ItemsContainer" just to hold the details on the Items tag. If you didn't need the "Number" attribute, then you could probably find a way to do away with this. However, this should get you in the right direction.
The example I provided is a simple version of how I usually handle the situation, obviously there's some improvements you can make depending on your needs.
Edit
I changed the Item class to use ItemName instead of TransactionId. It was an oversight on my part.
Edit 2
Here's the corrections you need to make to the newly posted code. The reason the Order class worked in the previous example was it matched the root XML element. You're new XML does align with the base class. So we need to add in a couple more attributes to make this work. You can also remove your ItemReply class. It's not needed.
So here's the new classes:
[XmlRoot("ITEM_REPLY")]
public class ItemAvlReply
{
[XmlElement("TRAN_ID")]
public string TRAN_ID { get; set; }
[XmlElement("REPLY_CODE")]
public string REPLY_CODE { get; set; }
[XmlElement("UNIT_PRICE")]
public string UNIT_PRICE { get; set; }
[XmlElement("SUP_LOCS")]
public SupplierLocations SUP_LOCS;
[XmlElement("MESSAGE")]
public string MESSAGE { get; set; }
[XmlElement("QTY_BREAKS")]
public string QTY_BREAKS { get; set; }
}
public class SupplierLocations
{
[XmlElement("SUP_LOC")]
public List<SupplierLocation> SUP_LOC;
}
public class SupplierLocation
{
[XmlElement("SUP_LOC_ID")]
public string SUP_LOC_ID { get; set; }
[XmlElement("COUNTRY_ID")]
public string COUNTRY_ID { get; set; }
[XmlElement("QTY_AVL")]
public string QTY_AVL { get; set; }
[XmlElement("ITEM_UOM")]
public string ITEM_UOM { get; set; }
}
Everything else should remain the same. The parsing/converting the XML to classes should work without any changes.

C# XML Deserialize Array Element Values

I've managed to deserialize my XML for the most part, but I'm stuck on one particular thing. How do I get the value of an element, if it's in an array, and each item in that array has it's own attributes. Let me show you what I have
<BusinessObject Name="BusinessName" RecID="12345">
<FieldList>
<Field Name="Field1">FieldValue1</Field>
<Field Name="Field2">FieldValue2</Field>
</FieldList>
</BusinessObject>
So this is a cut-down version, but shows the basis of the XML. I'm currently having trouble trying to capture "FieldValue1" and "FieldValue2" in their respective Field elements.
[XmlRoot("BusinessObject")]
public sealed class BusinessObject
{
[XmlAttribute("Name")]
public string Name { get; set; }
[XmlAttribute("RecID")]
public string RecID { get; set; }
[XmlElement("FieldList", Type = typeof(FieldList))]
public FieldList FieldList { get; set; }
public BusinessObject()
{
FieldList = null;
}
public static BusinessObject FromXmlString(string xmlString)
{
var reader = new StringReader(xmlString);
var serializer = new XmlSerializer(typeof(BusinessObject));
var instance = (BusinessObject)serializer.Deserialize(reader);
return instance;
}
}
[Serializable]
public class FieldList
{
[XmlElement("Field", Type = typeof(Field))]
public Field[] Fields { get; set; }
public FieldList()
{
Fields = null;
}
}
[Serializable]
public class Field
{
[XmlAttribute("Name")]
public string Name { get; set; }
public Field()
{
}
}
I'm sure it's just something simple that I'm missing, but I was having trouble trying to put my problem into words to perform a relevant search.
Many thanks,
Mark
Thanks for taking a look. Clearly I should play around more before posting here, as I've just managed to get it working. I had to add the following to the Field class:
[Serializable]
public class Field
{
[XmlAttribute("Name")]
public string Name { get; set; }
[XmlText]
public string Value { get; set; }
public Field()
{
}
}
Unfortunately I'm just running on a copy of express here, so I couldn't work out how to use xsd without the command prompt.
Cheers all,
Mark

Categories

Resources