Deserializing complicated xml into objects - Problems creating the objects - c#

Ok so I am working on deserializing an xml document into objects.
Basically my xml document will look like this:
<?xml version="1.0"?>
<root>
<eConnect CUSTNMBR="22222" DATE1="1900-01-01T00:00:00" TABLENAME="RM00101" DBNAME="BOLT" Requester_DOCTYPE="Customer" ACTION="0">
<Customer>
<CUSTNMBR>22222</CUSTNMBR>
<ADDRESS1>123 ABC St</ADDRESS1>
<ADDRESS2/>
<ADDRESS3/>
<ADRSCODE>PRIMARY</ADRSCODE>
<CITY>Ann Arbor</CITY>
<CNTCPRSN/>
<COUNTRY>USA</COUNTRY>
<CPRCSTNM/>
<CURNCYID/>
<CUSTCLAS/>
<CUSTDISC>0</CUSTDISC>
<CUSTNAME>Test Customer 2</CUSTNAME>
<PHONE1>4165551234</PHONE1>
<PHONE2/>
<PHONE3/>
<FAX/>
<PYMTRMID/>
<SALSTERR/>
<SHIPMTHD/>
<SLPRSNID/>
<STATE>Micigan</STATE>
<TAXSCHID/>
<TXRGNNUM/>
<UPSZONE/>
<ZIP>45612</ZIP>
<STMTNAME>Test Customer 2</STMTNAME>
<SHRTNAME>Test Customer 2</SHRTNAME>
<PRBTADCD>PRIMARY</PRBTADCD>
<PRSTADCD>PRIMARY</PRSTADCD>
<STADDRCD>PRIMARY</STADDRCD>
<CHEKBKID/>
<CRLMTTYP>0</CRLMTTYP>
<CRLMTAMT>0.00000</CRLMTAMT>
<CRLMTPER>0</CRLMTPER>
<CRLMTPAM>0.00000</CRLMTPAM>
<RATETPID/>
<PRCLEVEL/>
<MINPYTYP>0</MINPYTYP>
<MINPYDLR>0.00000</MINPYDLR>
<MINPYPCT>0</MINPYPCT>
<FNCHATYP>0</FNCHATYP>
<FNCHPCNT>0</FNCHPCNT>
<FINCHDLR>0.00000</FINCHDLR>
<MXWOFTYP>0</MXWOFTYP>
<MXWROFAM>0.00000</MXWROFAM>
<COMMENT1>test comment</COMMENT1>
<COMMENT2>another test</COMMENT2>
<USERDEF1/>
<USERDEF2/>
<TAXEXMT1/>
<TAXEXMT2/>
<BALNCTYP>0</BALNCTYP>
<STMTCYCL>5</STMTCYCL>
<BANKNAME/>
<BNKBRNCH/>
<FRSTINDT>1900-01-01T00:00:00</FRSTINDT>
<INACTIVE>0</INACTIVE>
<HOLD>0</HOLD>
<CRCARDID/>
<CRCRDNUM/>
<CCRDXPDT>1900-01-01T00:00:00</CCRDXPDT>
<KPDSTHST>1</KPDSTHST>
<KPCALHST>1</KPCALHST>
<KPERHIST>1</KPERHIST>
<KPTRXHST>1</KPTRXHST>
<CREATDDT>2015-11-27T00:00:00</CREATDDT>
<MODIFDT>2015-11-27T00:00:00</MODIFDT>
<Revalue_Customer>1</Revalue_Customer>
<Post_Results_To>0</Post_Results_To>
<FINCHID/>
<GOVCRPID/>
<GOVINDID/>
<DISGRPER>0</DISGRPER>
<DUEGRPER>0</DUEGRPER>
<DOCFMTID/>
<Send_Email_Statements>0</Send_Email_Statements>
<GPSFOINTEGRATIONID/>
<INTEGRATIONSOURCE>0</INTEGRATIONSOURCE>
<INTEGRATIONID/>
<Address>
<CUSTNMBR>22222</CUSTNMBR>
<ADRSCODE>PRIMARY</ADRSCODE>
<SLPRSNID/>
<UPSZONE/>
<SHIPMTHD/>
<TAXSCHID/>
<CNTCPRSN/>
<ADDRESS1>123 ABC St</ADDRESS1>
<ADDRESS2/>
<ADDRESS3/>
<COUNTRY>USA</COUNTRY>
<CITY>Ann Arbor</CITY>
<STATE>Micigan</STATE>
<ZIP>45612</ZIP>
<PHONE1>4165551234</PHONE1>
<PHONE2/>
<PHONE3/>
<FAX/>
<GPSFOINTEGRATIONID/>
<INTEGRATIONSOURCE>0</INTEGRATIONSOURCE>
<INTEGRATIONID/>
<Internet_Address/>
</Address>
</Customer>
</eConnect>
</root>
When I try to deserialize this, I get an "Object reference not set to instance of an object" exception. My classes look like this:
[XmlRoot("root")]
public class eConnect
{
public Customer customer;
}
public class Customer
{
public string CUSTNMBR { get; set; }
public string CUSTNAME { get; set; }
}
Am I getting the exception because I have to have variables for all the nodes in the xml document? Is there a way to only use the nodes I need?
Also am I creating my classes the right way, because basically the xml will have a root node called root and the root can contain multiple eConnect nodes, but each eConnect node will only contain one customer node.

Try adding the Serializable attribute on your classes and using the XmlElement tag inside your classes to define how the objects are mapped. I would also make a separate class for your root.
[XmlRoot("root")]
[Serializable]
public class Root
{
public Root()
{
eConnects = new List<eConnect>();
}
[XmlElement("eConnect")]
public List<eConnect> eConnects { get; set; }
}
[XmlRoot("eConnect")]
[Serializable]
public class eConnect
{
[XmlElement("Customer")]
public Customer customer { get; set; }
}
[XmlRoot("Customer")]
[Serializable]
public class Customer
{
[XmlElement("CUSTNMBR")]
public string CUSTNMBR { get; set; }
[XmlElement("CUSTNAME")]
public string CUSTNAME { get; set; }
}
Using the XmlElement tag also gives you the freedom to name your variables friendlier names. For example you could switch
[XmlElement("CUSTNAME")]
public string CUSTNAME { get; set; }
to
[XmlElement("CUSTNAME")]
public string CustomerName { get; set; }

Use Visual Studio's Paste XML as Classes feature.
To do this: Open Visual Studio > Edit Menu > Paste Special > Paste XML as Classes.
The resulting classes may need clean up (e.g. change type from ushort to int etc.). This feature handles the necessary attributes for serialization. Here is an example for your xml:
/// <remarks/>
[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = false)]
public partial class root
{
private rootEConnect eConnectField;
/// <remarks/>
public rootEConnect eConnect
{
get
{
return this.eConnectField;
}
set
{
this.eConnectField = value;
}
}
}
// Intermediate Xml nodes ...
/// <remarks/>
[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
public partial class rootEConnectCustomer
{
private ushort cUSTNMBRField;
private string aDDRESS1Field;
private object aDDRESS2Field;
private object aDDRESS3Field;
private string aDRSCODEField;
// rest of class ...
}

Related

xml Deserialize a list of objects containing a list of objects in c#

I'm trying to deserialize a list of objects that in turn contain lists of other object. What I've got is the following.
The actual deserializing is done by the framework in a ApiContoller httpPost request. The endpoint looks like this:
[HttpPost]
public async Task<HttpResponseMessage> MethodCall([FromBody] XmlEntries entries)
{
....
}
The XmlEntries class looks like this:
[XmlRoot("Root")]
public class XmlEntries
{
[XmlArrayItem("XmlEntry")]
public List<XmlEntry> XmlEntries{ get; set; }
public XmlEntries()
{
XmlEntries = new List<XmlEntry>();
}
public XmlEntries(IEnumerable<XmlEntry> entries)
{
XmlEntries= entries.ToList();
}
}
The XmlEntry class look like this:
public class XmlEntry
{
[XmlArrayItem("XmlSubEntry")]
public List<XmlSubEntry> XmlSubEntries{ get; set; }
}
and the XmlSubEntry looks like this.
public class XmlSubEntry
{
string AttributeOne{ get; set; }
int? AttributeTwo{ get; set; }
}
I've been using fiddler to send the following XML
<?xml version="1.0" encoding="utf-8"?>
<Root xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<XmlEntries>
<XmlEntry>
<XmlSubEntries>
<XmlSubEntry>
<AttributeOne>P</AttributeOne>
<AttributeTwo>8</AttributeTwo>
</XmlSubEntry>
<XmlSubEntry>
<AttributeOne>S</AttributeOne>
<AttributeTwo>26</AttributeTwo>
</XmlSubEntry>
</XmlSubEntries>
</XmlEntry>
</XmlEntries>
</Root>
My problem is that the attributes of XmlSubEntry never gets correctly serialized. When i debug the MethodCall in apiController entries will be a list containing 1 XmlEntry with XmlSubEntries being a list of 2 XmlSubEntry, but the attributes (AttributeOne and AttributeTwo) are always null.
I have tried annotating the classes In all ways I can thing of but I still won't get the attributes to serialize correctry.
Is there any XML-ninjas around that can help me figure out what I'm doing wrong?
The best tip I can give you is to do this in reverse - add some data and serialise it to XML, see what it looks like. That will usually point to where you're wrong.
In this case, however, you're very close. The only issue you have is that you can't serialise private properties, so make them public:
public class XmlSubEntry
{
public string AttributeOne { get; set; }
public int? AttributeTwo { get; set; }
}

how to change root element of api output

I have created an api that gives the output in form of xml. It is used to give sitemap for the website. It just includes the following code
public class SitemapApiController : ApiController
{
public urlset GetSitemap()
{
// code for data access etc
return urlsetObj;
}
}
the urlset class is very simple
[XmlRoot("urlset")]
public class urlset: List<url>
{
}
the url class is as follows
public class url
{
public string loc { get; set; }
public DateTime lastmod { get; set; }
public double priority { get; set; }
}
Now my problem is that output is coming as
<arrayofurls>
<url>.....
while i want it to be
<urlset>
<url>....
I have read the similar posts for this, but to no relief. As stated in Modify the xml array element name in serialized ASP.NET WebAPI object post, if i use
[XmlArray("urlset")]
atribute over api controller, then it gives error that attribute "XmlArray" is not valid in this declaration type. Pls help me so that i can return valid sitemap from the api.
Edit
The output according to this is still a bit different. Now it shows
<urlset>
<url>
<url>
<loc>
<url>........
There is an extra needless url attribute as parent of all the url attributes.
UPDATED
It took a little bit of work, but I believe I found what you are looking for.
A WebApi project should serialize XML using the namespace System.Runtime.Serialization, not the namespace System.Xml.Serialization.
Using this new serialization method, it becomes extremely simple to output the results exactly as you want.
The api controller GET method:
public urlset Get()
{
return new urlset
{
new url { loc = "1" },
new url { loc = "2" },
new url { loc = "3" }
};
}
The urlset class:
[CollectionDataContract(Name ="urlset", Namespace ="")]
public class urlset:List<url>
{
}
The url class:
[DataContract(Namespace = "")]
public class url
{
[DataMember]
public string loc { get; set; }
[DataMember]
public DateTime lastmod { get; set; }
[DataMember]
public double priority { get; set; }
}
The output:
<urlset xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<url>
<lastmod>2016-10-19T08:07:28.6867397-07:00</lastmod>
<loc>1</loc>
<priority>0</priority>
</url>
<url>
<lastmod>2016-10-15T08:07:28.6867397-07:00</lastmod>
<loc>2</loc>
<priority>0</priority>
</url>
<url>
<lastmod>2016-09-20T08:07:28.6867397-07:00</lastmod>
<loc>3</loc>
<priority>0</priority>
</url>
</urlset>
I realize the example you give in your question does not have the xmlns attribute in the root node. However, it seems there is no "easy" way to remove that.
Here are the resources I used to arrive at this solution:
StackOverflow: How to serialize xml into desired format in asp.net web api
MSDN: Collection Types in Data Contracts

WCF Rest Datamember and Array issue

I've created a WCF service and the service is receiving the XML structure with all the data but the PackageID and ServiceCode. The problem seems to be in public RequestPackages[] Packages in the Track class.
If I change it to RequestPackages in the code below then the element data will be passed into the service method and will show in the PackageID/ServiceCode elements.
If I have it as RequestPackages[] then I get this stepping through in debug in the xml for Packages Element: wcf.RequestPackages[0] and PackageID/ServiceCode are not available. I am stepping through debug to view xml data passed as it hits the service method. I'm not sure how to resolve it but I've probably overlooked something simple. Thanks
Below is the Xml structure being sent:
<Track>
<Packages>
<PackageId>1234567890</PackageId>
<ServiceCode>123</ServiceCode>
</Packages>
</Track>
Below is the Data contracts:
[DataContract(Namespace = "")]
[XmlArrayItemAttribute("Package")]
public partial class Track
{
private RequestPackages[] packagesField;
/// <remarks/>
[DataMember(Order=0, Name="Package")]
public RequestPackages[] Packages
{
get
{
return this.packagesField;
}
set
{
this.packagesField = value;
}
}
}
[DataContract(Namespace = "")]
[XmlSerializerFormat]
public partial class RequestPackages
{
private string packageIdField;
private string serviceCodeField;
/// <remarks/>
[DataMember(Order = 0)]
[XmlElementAttribute]
public string PackageId
{
get
{
return this.packageIdField;
}
set
{
this.packageIdField = value;
}
}
/// <remarks/>
[DataMember(Order=1)]
[XmlElementAttribute]
public string ServiceCode
{
get
{
return this.serviceCodeField;
}
set
{
this.serviceCodeField = value;
}
}
}
I was missing an element in the xml...
<Packages>
<RequestPackages>
<PackageId>1234567890</PackageId>
<ServiceCode></ServiceCode>
</RequestPackages>
</Packages>

How to exclude a property from DataContractSerialization When It Has No Data?

I'm using DataContractSerializer in my Web API application and in my action I'm returning a Data Type as below:
public class Event
{
public string Name {get; set;}
public IList<Division> Divisions {get;set;}
}
When serialized it's returning the below xml:
<Event xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07
/EventTypeNameSpace">
<Name>some name</Name>
<Divisions i:nil="true" />
</Event>
1) Why is it returning two xmlns:i and xmlns attributes? how can they be excluded?
2) How can I exclude the Divisions from the xml when it's null?
1: the "http://schemas.datacontract.org/2004/07" is the default namespace used by types serialized by data-contract serializer; if you don't like that - change your contract; the "http://www.w3.org/2001/XMLSchema-instance" defines "nil" as a special value
2: by defining the contract properly
[DataContract(Namespace="")]
public class Event
{
[DataMember]
public string Name { get; set; }
[DataMember(EmitDefaultValue=false)]
public IList<Division> Divisions { get; set; }
}
However: I should add - if you want tight control over what the layout looks like, you should probably be using XmlSerializer, not DataContractSerializer

Deserializing XML where each object has a list of objects

I'm working on a WP7 app which gets and updates data on a web server. If any updates need a response, I get a list of errors that needs to be dealt with, and a list of possible choices for each error. The issue I'm having is assigning each object its appropriate list of choices. As of now I get a list of errors, and another list of all possible choices for all errors. I'd like the error object to contain the list of only its options so I can handle that.
So here's an example response:
<?xml version="1.0" encoding="utf-8"?>
<response xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<response_error_dialogs>
<error_dialog_list>
<error_dialog_choice>
<error_dialog_id>1301</error_dialog_id>
<error_dialog_message>You have changed the phone number. Select which phone number to make the primary contact number.</error_dialog_message>
<error_dialog_title>Phone Number Changed</error_dialog_title>
<error_dialog_is_set>false</error_dialog_is_set>
<error_dialog_choice_option_list>
<error_dialog_choice_option>
<error_dialog_choice_option_id>1</error_dialog_choice_option_id>
<error_dialog_choice_option_title>Home</error_dialog_choice_option_title>
</error_dialog_choice_option>
<error_dialog_choice_option>
<error_dialog_choice_option_id>2</error_dialog_choice_option_id>
<error_dialog_choice_option_title>Mobile</error_dialog_choice_option_title>
</error_dialog_choice_option>
<error_dialog_choice_option>
<error_dialog_choice_option_id>3</error_dialog_choice_option_id>
<error_dialog_choice_option_title>Work</error_dialog_choice_option_title>
</error_dialog_choice_option>
</error_dialog_choice_option_list>
</error_dialog_choice>
<error_dialog_choice>
<error_dialog_id>1303</error_dialog_id>
<error_dialog_message>You have changed the account email address. Would you like this to be the new default email?</error_dialog_message>
<error_dialog_title>Email Address Changed</error_dialog_title>
<error_dialog_is_set>false</error_dialog_is_set>
<error_dialog_choice_option_list>
<error_dialog_choice_option>
<error_dialog_choice_option_id>1</error_dialog_choice_option_id>
<error_dialog_choice_option_title>No</error_dialog_choice_option_title>
</error_dialog_choice_option>
<error_dialog_choice_option>
<error_dialog_choice_option_id>2</error_dialog_choice_option_id>
<error_dialog_choice_option_title>Yes</error_dialog_choice_option_title>
</error_dialog_choice_option>
</error_dialog_choice_option_list>
</error_dialog_choice>
</error_dialog_list>
</response_error_dialogs>
</response>
And here's the classes used:
public class ErrorDialog
{
XElement self;
public ErrorDialog() { }
public ErrorDialog(XElement errorDialog)
{
self = errorDialog;
}
public int errorDialogId
{
get { return (int)(self.Element("error_dialog_id")); }
}
public string errorDialogMessage
{
get { return (string)(self.Element("error_dialog_message")); }
}
public string errorDialogTitle
{
get { return (string)(self.Element("error_dialog_title")); }
}
public bool errorDialogIsSet
{
get { return (bool)(self.Element("error_dialog_is_set")); }
}
public List<ErrorDialogChoice> errorDialogChoice
{
get { return (List<ErrorDialogChoice>)(errorDialogChoice); }
}
public int errorDialogSelectedOption
{
get { return (int)(self.Element("error_dialog_selected_option")); }
}
}
class ErrorDialogChoice
{
XElement self;
public ErrorDialogChoice() { }
public ErrorDialogChoice(XElement errorDialogChoice)
{
self = errorDialogChoice;
}
public int errorDialogChoiceOptionId
{
get { return (int)(self.Element("error_dialog_choice_option_id")); }
}
public string errorDialogChoiceOptionTitle
{
get { return (string)(self.Element("error_dialog_choice_option_title")); }
}
}
And here's how I'm parsing it:
XElement response = XElement.Parse(data);
ErrorDialog[] dialogs = response
.Element("response_error_dialogs")
.Element("error_dialog_list")
.Elements("error_dialog_choice")
.Select(e => new ErrorDialog(e))
.ToArray();
ErrorDialogChoice[] edChoices = response
.Element("response_error_dialogs")
.Element("error_dialog_list")
.Element("error_dialog_choice")
.Element("error_dialog_choice_option_list")
.Elements("error_dialog_choice_option")
.Select(e => new ErrorDialogChoice(e))
.ToArray();
So with this example, the first error_dialog_choice object will have a List containing 3 error_dialog_choice_option objects, the second has the two error_dialog_choice_option objects, and any more that may come back. Any help is appreciated. Thanks.
You can use XML serialization to achieve this much easier:
var reader = new StringReader(xmlString);
var ser = new XmlSerializer(typeof(Response));
var result = (Response) ser.Deserialize(reader);
Using these class definitions.
[XmlType("response")]
public class Response
{
[XmlElement("response_error_dialogs")]
public ErrorDialog ErrorDialog;
}
[XmlType("response_error_dialogs")]
public class ErrorDialog
{
[XmlArray("error_dialog_list")]
public List<ChoiceErrorDialog> ChoiceList;
}
[XmlType("error_dialog_choice")]
public class ChoiceErrorDialog
{
[XmlElement("error_dialog_id")]
public int Id;
[XmlElement("error_dialog_message")]
public string Message;
[XmlElement("error_dialog_title")]
public string Title;
[XmlElement("error_dialog_is_set")]
public bool IsSet;
[XmlArray("error_dialog_choice_option_list")]
public List<Option> OptionList;
}
[XmlType("error_dialog_choice_option")]
public class Option
{
[XmlElement("error_dialog_choice_option_id")]
public int Id;
[XmlElement("error_dialog_choice_option_title")]
public string Title;
}
I am guessing there can be more types of error dialogs, and <error_dialog_choice> is just one of the possible types. In this case you could use subclassing, and list the subclasses with XmlArrayItem attributes.
You could also generate the class definitions with xsd.exe or svcutil.exe, from an .xsd or .wsdl file. xsd.exe can even infer the schema from a sample .xml file.
xsd.exe /?
svcutil.exe /?
Use the XmlSerializer built into the framework. Use attributes to map out the xml equivalent of your classes and their properties.
Example:
[XmlElement("Txn")]
public List<Transaction> Items { get; set; }
You can use the XSD.exe to generate an XSD and then generate C# code for your XSD. Use Visual Studio Command Prompt with a sample response.xml file. Eg:
c:>xsd response.xml
c:>xsd response.xsd /c /edb

Categories