XML Serialization issue - duplicate XmlRoot node - c#

I am working with a third party XML API, that returns XML responses. With the API, I am trying to import orders. So, two scenario could occur when I import order with this API:
Success - e.g. xml response: http://pastebin.com/ZcvvYMX6
Failure - e.g. xml response: http://pastebin.com/iVjqMKAR
What I am working on is a wrapper for this API and I have a method called ImportOrders. Within this method, I am trying to deserialize the xml to an object so that I can return a common response.
For example; I will have a common response DTO class like this:
public class ImportOrderResponse
{
bool IsError { get; set; }
string ErrorMsg { get; set; }
string Result { get; set; }
}
and the ImportOrders method will return response conditionally like this:
if (apiResult.Contains("importFailures"))
{
// todo: deserialize
return new ImportOrderResponse()
{
IsError = true,
ErrorMsg = "todo - get failureMessage"
};
}
else
{
// todo: deserialize
return new ImportOrderResponse()
{
IsError = false,
Result = "todo - haven't worked out what i need to return"
};
}
Using this site http://xmltocsharp.azurewebsites.net I've generated the classes used for deserialization like this:
ImportSuccess.cs
[XmlRoot(ElementName = "import")]
public class ImportSuccess
{
[XmlAttribute(AttributeName = "type")]
public string Type { get; set; }
[XmlAttribute(AttributeName = "operation")]
public string Operation { get; set; }
[XmlAttribute(AttributeName = "entity")]
public string Entity { get; set; }
[XmlAttribute(AttributeName = "externalReference")]
public string ExternalReference { get; set; }
[XmlAttribute(AttributeName = "item")]
public string Item { get; set; }
[XmlAttribute(AttributeName = "queryTime")]
public string QueryTime { get; set; }
}
[XmlRoot(ElementName = "importSuccesses")]
public class ImportSuccesses
{
[XmlElement(ElementName = "import")]
public List<ImportSuccess> Import { get; set; }
}
[XmlRoot(ElementName = "importResult")]
public class ImportResult
{
[XmlElement(ElementName = "importSuccesses")]
public ImportSuccesses ImportSuccesses { get; set; }
[XmlElement(ElementName = "importFailures")]
public string ImportFailures { get; set; }
[XmlElement(ElementName = "importDuplicates")]
public string ImportDuplicates { get; set; }
}
ImportFailure.cs
[XmlRoot(ElementName = "import")]
public class ImportFailure
{
[XmlElement(ElementName = "failureMessage")]
public string FailureMessage { get; set; }
[XmlElement(ElementName = "failureDetail")]
public string FailureDetail { get; set; }
[XmlAttribute(AttributeName = "type")]
public string Type { get; set; }
[XmlAttribute(AttributeName = "operation")]
public string Operation { get; set; }
[XmlAttribute(AttributeName = "externalReference")]
public string ExternalReference { get; set; }
[XmlAttribute(AttributeName = "queryTime")]
public string QueryTime { get; set; }
}
[XmlRoot(ElementName = "importFailures")]
public class ImportFailures
{
[XmlElement(ElementName = "import")]
public ImportFailure Import { get; set; }
}
[XmlRoot(ElementName = "importResult")]
public class ImportResult
{
[XmlElement(ElementName = "importSuccesses")]
public string ImportSuccesses { get; set; }
[XmlElement(ElementName = "importFailures")]
public ImportFailures ImportFailures { get; set; }
[XmlElement(ElementName = "importDuplicates")]
public string ImportDuplicates { get; set; }
}
But now I have a problem. Both the success & failure response contains the node importResult but has varying fields/element names.
Now I have an error because I have a duplicate ImportResult class.
How can I solve this? What is the correct way to handle this scenario?

I believe I was being silly... I was going about this with a wrong approach.
What I did was;
Call api with correct data for a success response, took the xml and created a class
call api with wrong/bad data for a failure response, took the xml and tried to create another class
After some digging around, I realised that the response structure is very close for success/failure.
So, I called the api and instead of sending one order; I sent 3 orders for 3 specific scenario. This resulted in a single XML response like this:
http://pastebin.com/sNgWAwq1
Now I have a good structure of the XML for (Success/Failure & Duplicates). Using this XML, I've generated the following class (for deserialisation purpose):
[XmlRoot(ElementName = "import")]
public class Import
{
[XmlAttribute(AttributeName = "type")]
public string Type { get; set; }
[XmlAttribute(AttributeName = "operation")]
public string Operation { get; set; }
[XmlAttribute(AttributeName = "entity")]
public string Entity { get; set; }
[XmlAttribute(AttributeName = "externalReference")]
public string ExternalReference { get; set; }
[XmlAttribute(AttributeName = "item")]
public string Item { get; set; }
[XmlAttribute(AttributeName = "queryTime")]
public string QueryTime { get; set; }
[XmlElement(ElementName = "failureMessage")]
public string FailureMessage { get; set; }
[XmlElement(ElementName = "failureDetail")]
public string FailureDetail { get; set; }
[XmlElement(ElementName = "duplicateMessage")]
public string DuplicateMessage { get; set; }
[XmlElement(ElementName = "duplicateDetail")]
public string DuplicateDetail { get; set; }
}
[XmlRoot(ElementName = "importSuccesses")]
public class ImportSuccesses
{
[XmlElement(ElementName = "import")]
public List<Import> Import { get; set; }
}
[XmlRoot(ElementName = "importFailures")]
public class ImportFailures
{
[XmlElement(ElementName = "import")]
public Import Import { get; set; }
}
[XmlRoot(ElementName = "importDuplicates")]
public class ImportDuplicates
{
[XmlElement(ElementName = "import")]
public Import Import { get; set; }
}
[XmlRoot(ElementName = "importResult")]
public class OrderImportResponse
{
[XmlElement(ElementName = "importSuccesses")]
public ImportSuccesses ImportSuccesses { get; set; }
[XmlElement(ElementName = "importFailures")]
public ImportFailures ImportFailures { get; set; }
[XmlElement(ElementName = "importDuplicates")]
public ImportDuplicates ImportDuplicates { get; set; }
}
Now, this can work for me. I can call the api and deserialise the response to the OrderImportResponse class (without checking what kind of xml response it was) and it will have all three scenario responses I am looking for.

Related

How do I deserialize an XML API response in ASP.NET?

I am working on a project that calls an API (using C# ASP.NET), and the API returns an XML document. I know how to deserialize an response in JSON, but am running into issues with deserializing an XML response. Currently I am getting the below error when attempting to deserialize the XML:
InvalidOperationException: response xmlns="" was not expected.
The error code displayed just the empty quotes in the error message. I have the code samples below, and I would greatly appreciate any constructive feedback or advice on making this better, and any advice on where I went wrong on attempting to deserialize this!
Here is the XML response from the API call (This API only returns either CML or a .csv):
<response xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.2" xsi:noNamespaceSchemaLocation="http://www.aviationweather.gov/static/adds/schema/metar1_2.xsd">
<request_index>554967903</request_index>
<data_source name="metars"/>
<request type="retrieve"/>
<errors/>
<warnings/>
<time_taken_ms>7</time_taken_ms>
<data num_results="3">
<METAR>
<raw_text>KDEN 102353Z 15007KT 10SM FEW220 07/M08 A3038 RMK AO2 SLP285 T00721083 10133 20072 58015</raw_text>
<station_id>KDEN</station_id>
<observation_time>2022-01-10T23:53:00Z</observation_time>
<latitude>39.85</latitude>
<longitude>-104.65</longitude>
<temp_c>7.2</temp_c>
<dewpoint_c>-8.3</dewpoint_c>
<wind_dir_degrees>150</wind_dir_degrees>
<wind_speed_kt>7</wind_speed_kt>
<visibility_statute_mi>10.0</visibility_statute_mi>
<altim_in_hg>30.380905</altim_in_hg>
<sea_level_pressure_mb>1028.5</sea_level_pressure_mb>
<quality_control_flags>
<auto_station>TRUE</auto_station>
</quality_control_flags>
<sky_condition sky_cover="FEW" cloud_base_ft_agl="22000"/>
<flight_category>VFR</flight_category>
<three_hr_pressure_tendency_mb>-1.5</three_hr_pressure_tendency_mb>
<maxT_c>13.3</maxT_c>
<minT_c>7.2</minT_c>
<metar_type>METAR</metar_type>
<elevation_m>1656.0</elevation_m>
</METAR>
<METAR>
<raw_text>KSEA 102353Z 34003KT 6SM -RA BR FEW015 BKN035 OVC045 08/06 A3035 RMK AO2 SLP288 P0000 60001 T00780056 10083 20044 50003</raw_text>
<station_id>KSEA</station_id>
<observation_time>2022-01-10T23:53:00Z</observation_time>
<latitude>47.45</latitude>
<longitude>-122.32</longitude>
<temp_c>7.8</temp_c>
<dewpoint_c>5.6</dewpoint_c>
<wind_dir_degrees>340</wind_dir_degrees>
<wind_speed_kt>3</wind_speed_kt>
<visibility_statute_mi>6.0</visibility_statute_mi>
<altim_in_hg>30.351377</altim_in_hg>
<sea_level_pressure_mb>1028.8</sea_level_pressure_mb>
<quality_control_flags>
<auto_station>TRUE</auto_station>
</quality_control_flags>
<wx_string>-RA BR</wx_string>
<sky_condition sky_cover="FEW" cloud_base_ft_agl="1500"/>
<sky_condition sky_cover="BKN" cloud_base_ft_agl="3500"/>
<sky_condition sky_cover="OVC" cloud_base_ft_agl="4500"/>
<flight_category>VFR</flight_category>
<three_hr_pressure_tendency_mb>0.3</three_hr_pressure_tendency_mb>
<maxT_c>8.3</maxT_c>
<minT_c>4.4</minT_c>
<precip_in>0.005</precip_in>
<pcp6hr_in>0.01</pcp6hr_in>
<metar_type>METAR</metar_type>
<elevation_m>115.0</elevation_m>
</METAR>
<METAR>
<raw_text>PHNL 102353Z 19009KT 10SM FEW025 FEW035 SCT050 26/21 A2997 RMK AO2 SLP147 T02560206 10261 20200 58017</raw_text>
<station_id>PHNL</station_id>
<observation_time>2022-01-10T23:53:00Z</observation_time>
<latitude>21.33</latitude>
<longitude>-157.93</longitude>
<temp_c>25.6</temp_c>
<dewpoint_c>20.6</dewpoint_c>
<wind_dir_degrees>190</wind_dir_degrees>
<wind_speed_kt>9</wind_speed_kt>
<visibility_statute_mi>10.0</visibility_statute_mi>
<altim_in_hg>29.970472</altim_in_hg>
<sea_level_pressure_mb>1014.7</sea_level_pressure_mb>
<quality_control_flags>
<auto_station>TRUE</auto_station>
</quality_control_flags>
<sky_condition sky_cover="FEW" cloud_base_ft_agl="2500"/>
<sky_condition sky_cover="FEW" cloud_base_ft_agl="3500"/>
<sky_condition sky_cover="SCT" cloud_base_ft_agl="5000"/>
<flight_category>VFR</flight_category>
<three_hr_pressure_tendency_mb>-1.7</three_hr_pressure_tendency_mb>
<maxT_c>26.1</maxT_c>
<minT_c>20.0</minT_c>
<metar_type>METAR</metar_type>
<elevation_m>2.0</elevation_m>
</METAR>
</data>
</response>
This is the code to call the API:
private static readonly HttpClient client = new HttpClient();
static async Task Main(string[] args)
{
client.DefaultRequestHeaders.Accept.Clear();
string baseUrl = "https://www.aviationweather.gov/adds/dataserver_current/httpparam?dataSource=metars&requestType=retrieve&format=xml&stationString=KMSP&hoursBeforeNow=2";
client.BaseAddress = new Uri(baseUrl);
HttpResponseMessage result = client.GetAsync(baseUrl).Result;
XmlSerializer serialObject = new XmlSerializer(typeof(MetarReport));
List<MetarReport> MetarCollection = new List<MetarReport>();
if (result.IsSuccessStatusCode)
{
using (Stream reader = result.Content.ReadAsStreamAsync().Result)
{
MetarReport metar = new MetarReport();
metar = (MetarReport)serialObject.Deserialize(reader);
MetarCollection.Add(metar);
}
}
// Test deserializer
foreach(var item in MetarCollection)
{
Console.WriteLine(item.rawText);
}
Console.ReadLine();
}
And this is the object I am attempting to deserialize the XML into
[XmlRoot("METAR")]
[XmlType("METAR")]
public class MetarReport
{
[XmlElement("raw_text")]
public string rawText { get; set; }
[XmlElement("station_id")]
public string stationId { get; set; }
[XmlElement("latitude")]
public double latitiude { get; set; }
[XmlElement("longitude")]
public double longitude { get; set; }
[XmlElement("temp_c")]
public double tempCelsius { get; set; }
[XmlElement("dewpoint_c")]
public double dewpoint { get; set; }
[XmlElement("wind_dir_degree")]
public int windDirection { get; set; }
[XmlElement("wind_speed_kt")]
public double windspeed { get; set; }
[XmlElement("visibility_statute_mi")]
public double visbilityMiles { get; set; }
[XmlElement("altim_in_hg")]
public double altimeter { get; set; }
//[XmlElement("sky_condition")]
//public List<SkyCondition> skyConditions {get; set;}
[XmlElement("flight_category")]
public string flightCategory { get; set; }
[XmlElement("metar_type")]
public string metarType { get; set; }
[XmlElement("elevation_m")]
public double elevationMeters { get; set; }
}
try this, it was tested in Visual Studio
HttpResponseMessage response= client.GetAsync(baseUrl).Result;
string xml;
if (response.IsSuccessStatusCode)
{
xml = response.Content.ReadAsStringAsync().Result;
}
Response result;
XmlSerializer serializer = new XmlSerializer(typeof(Response));
using (StringReader reader = new StringReader(xml))
{
result = (Response)serializer.Deserialize(reader);
}
classes
[XmlRoot(ElementName = "response")]
public class Response
{
[XmlElement(ElementName = "request_index")]
public int RequestIndex { get; set; }
[XmlElement(ElementName = "data_source")]
public DataSource DataSource { get; set; }
[XmlElement(ElementName = "request")]
public Request Request { get; set; }
[XmlElement(ElementName = "errors")]
public object Errors { get; set; }
[XmlElement(ElementName = "warnings")]
public object Warnings { get; set; }
[XmlElement(ElementName = "time_taken_ms")]
public int TimeTakenMs { get; set; }
[XmlElement(ElementName = "data")]
public Data Data { get; set; }
[XmlAttribute(AttributeName = "xsd")]
public string Xsd { get; set; }
[XmlAttribute(AttributeName = "xsi")]
public string Xsi { get; set; }
[XmlAttribute(AttributeName = "version")]
public string Version { get; set; }
[XmlAttribute(AttributeName = "noNamespaceSchemaLocation")]
public string NoNamespaceSchemaLocation { get; set; }
[XmlText]
public string Text { get; set; }
}
[XmlRoot(ElementName = "quality_control_flags")]
public class QualityControlFlags
{
[XmlElement(ElementName = "auto_station")]
public string AutoStation { get; set; }
}
[XmlRoot(ElementName = "sky_condition")]
public class SkyCondition
{
[XmlAttribute(AttributeName = "sky_cover")]
public string SkyCover { get; set; }
[XmlAttribute(AttributeName = "cloud_base_ft_agl")]
public int CloudBaseFtAgl { get; set; }
}
[XmlRoot(ElementName = "METAR")]
public class METAR
{
[XmlElement(ElementName = "sky_condition")]
public List<SkyCondition> SkyCondition { get; set; }
[XmlElement(ElementName = "flight_category")]
public string FlightCategory { get; set; }
[XmlElement(ElementName = "three_hr_pressure_tendency_mb")]
public double ThreeHrPressureTendencyMb { get; set; }
[XmlElement(ElementName = "maxT_c")]
public decimal MaxTC { get; set; }
[XmlElement(ElementName = "minT_c")]
public decimal MinTC { get; set; }
[XmlElement(ElementName = "precip_in")]
public double PrecipIn { get; set; }
[XmlElement(ElementName = "pcp6hr_in")]
public double Pcp6hrIn { get; set; }
[XmlElement(ElementName = "metar_type")]
public string MetarType { get; set; }
[XmlElement(ElementName = "elevation_m")]
public double ElevationM { get; set; }
[XmlElement(ElementName = "raw_text")]
public string RawText { get; set; }
[XmlElement(ElementName = "station_id")]
public string StationId { get; set; }
[XmlElement(ElementName = "observation_time")]
public DateTime ObservationTime { get; set; }
[XmlElement(ElementName = "latitude")]
public double Latitude { get; set; }
[XmlElement(ElementName = "longitude")]
public double Longitude { get; set; }
[XmlElement(ElementName = "temp_c")]
public double TempC { get; set; }
[XmlElement(ElementName = "dewpoint_c")]
public double DewpointC { get; set; }
[XmlElement(ElementName = "wind_dir_degrees")]
public int WindDirDegrees { get; set; }
[XmlElement(ElementName = "wind_speed_kt")]
public int WindSpeedKt { get; set; }
[XmlElement(ElementName = "visibility_statute_mi")]
public double VisibilityStatuteMi { get; set; }
[XmlElement(ElementName = "altim_in_hg")]
public double AltimInHg { get; set; }
[XmlElement(ElementName = "sea_level_pressure_mb")]
public decimal SeaLevelPressureMb { get; set; }
[XmlElement(ElementName = "quality_control_flags")]
public QualityControlFlags QualityControlFlags { get; set; }
}
[XmlRoot(ElementName = "data")]
public class Data
{
[XmlElement(ElementName = "METAR")]
public List<METAR> METAR { get; set; }
[XmlAttribute(AttributeName = "num_results")]
public int NumResults { get; set; }
[XmlText]
public string Text { get; set; }
}
[XmlRoot(ElementName = "data_source")]
public class DataSource
{
[XmlAttribute(AttributeName = "name")]
public string Name { get; set; }
}
[XmlRoot(ElementName = "request")]
public class Request
{
[XmlAttribute(AttributeName = "type")]
public string Type { get; set; }
}

Can't deserialize xml properly

I have the following string to deserialize:
<result>
<error>
<errorcode>0</errorcode>
<errorge>წარმატებით</errorge>
<errorru>Удачно</errorru>
<erroren>successfully</erroren>
<line>89</line>
</error>
<amount>
<gel>1</gel>
</amount>
<user>01001</user>
<service>MyService</service> // here
<data>
<nickname>popcorn2</nickname>
<identification_name>identified</identification_name>
<wallet_code>5554654</wallet_code>
<NationalRate>1</NationalRate>
<RATE>1</RATE>
<GENERATED_AMOUNT>1</GENERATED_AMOUNT>
<CURRENCY>GEL</CURRENCY>
</data>
<accoutant>
<agentBenefit>0</agentBenefit>
<agentCommission>0.44</agentCommission>
<clientCommission>0</clientCommission>
</accoutant>
<service>
<min_amount>0.49</min_amount>
<max_amount>1500.00</max_amount>
<currency>GEL</currency>
</service>
<avance>-134206.1500</avance>
<operation_status>0</operation_status>
</result>
as you noticed it has two tags named "service" and they have different contents
here is the class that I've been using
[XmlRoot(ElementName = "error")]
public class ErrorInfo
{
[XmlElement(ElementName = "errorcode")]
public string Errorcode { get; set; }
[XmlElement(ElementName = "errorge")]
public string Errorge { get; set; }
[XmlElement(ElementName = "errorru")]
public string Errorru { get; set; }
[XmlElement(ElementName = "erroren")]
public string Erroren { get; set; }
[XmlElement(ElementName = "line")]
public string Line { get; set; }
}
[XmlRoot(ElementName = "amount")]
public class Amount
{
[XmlElement(ElementName = "gel")]
public string Gel { get; set; }
}
[XmlRoot(ElementName = "data")]
public class Data
{
[XmlElement(ElementName = "nickname")]
public string Nickname { get; set; }
[XmlElement(ElementName = "identification_name")]
public string Identification_name { get; set; }
[XmlElement(ElementName = "wallet_code")]
public string Wallet_code { get; set; }
[XmlElement(ElementName = "NationalRate")]
public string NationalRate { get; set; }
[XmlElement(ElementName = "RATE")]
public string RATE { get; set; }
[XmlElement(ElementName = "GENERATED_AMOUNT")]
public string GENERATED_AMOUNT { get; set; }
[XmlElement(ElementName = "CURRENCY")]
public string CURRENCY { get; set; }
}
[XmlRoot(ElementName = "accoutant")]
public class Accoutant
{
[XmlElement(ElementName = "agentBenefit")]
public string AgentBenefit { get; set; }
[XmlElement(ElementName = "agentCommission")]
public string AgentCommission { get; set; }
[XmlElement(ElementName = "clientCommission")]
public string ClientCommission { get; set; }
}
[XmlRoot(ElementName = "service")]
public class Service
{
[XmlElement(ElementName = "min_amount")]
public string Min_amount { get; set; }
[XmlElement(ElementName = "max_amount")]
public string Max_amount { get; set; }
[XmlElement(ElementName = "currency")]
public string Currency { get; set; }
}
[XmlRoot(ElementName = "result")]
public class Result
{
[XmlElement(ElementName = "error")]
public ErrorInfo Error { get; set; }
[XmlElement(ElementName = "amount")]
public Amount Amount { get; set; }
[XmlElement(ElementName = "user")]
public string User { get; set; }
[XmlElement(ElementName = "data")]
public Data Data { get; set; }
[XmlElement(ElementName = "accoutant")]
public Accoutant Accoutant { get; set; }
[XmlElement(ElementName = "service")]
public Service[] Service { get; set; }
[XmlElement(ElementName = "avance")]
public string Avance { get; set; }
[XmlElement(ElementName = "operation_status")]
public string Operation_status { get; set; }
}
I tried to handle service tags as array but it could not work. I was able to get values from the last tag , but not from the first one. both tags are neccessary so i am wondering if there is a way to get values from both tags ?
Visual Studio has this really great feature where you can put your XML in the clipboard and then do Edit -> Paste special -> Paste XML as classes.
If you do that with your XML, it generates the following class for the service tag:
/// <remarks/>
[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
public partial class resultService
{
private decimal min_amountField;
private decimal max_amountField;
private string currencyField;
private string[] textField;
/// <remarks/>
public decimal min_amount
{
get
{
return this.min_amountField;
}
set
{
this.min_amountField = value;
}
}
/// <remarks/>
public decimal max_amount
{
get
{
return this.max_amountField;
}
set
{
this.max_amountField = value;
}
}
/// <remarks/>
public string currency
{
get
{
return this.currencyField;
}
set
{
this.currencyField = value;
}
}
/// <remarks/>
[System.Xml.Serialization.XmlTextAttribute()]
public string[] Text
{
get
{
return this.textField;
}
set
{
this.textField = value;
}
}
}
If you want to do it manually, the secret seems to be to add a field with the XmlTextAttribute attribute.
You can not have two different elements with the same name. It won't know which tag belongs where, you have to alter the name to "MyServiceName" or something similar.
Change your classes as follows:
[XmlRoot(ElementName = "result")]
public class Result
{
// no need XmlElement attribute
public ServiceComplex ServiceComplex { get; set; }
// no need XmlElement attribute
public string ServiceSimple { get; set; }
// other properties
}
// no need XmlRoot attribute
public class ServiceComplex
{
[XmlElement(ElementName = "min_amount")]
public string Min_amount { get; set; }
[XmlElement(ElementName = "max_amount")]
public string Max_amount { get; set; }
[XmlElement(ElementName = "currency")]
public string Currency { get; set; }
}
Instead of ServiceComplex and ServiceSimple, choose the names that suit you.
Subscribe XmlSerializer to UnknownElement event:
var xs = new XmlSerializer(typeof(Result));
xs.UnknownElement += Xs_UnknownElement;
private static void Xs_UnknownElement(object sender, XmlElementEventArgs e)
{
if (e.Element.Name == "service")
{
var result = (Result)e.ObjectBeingDeserialized;
if (e.Element.ChildNodes.Count == 1)
{
result.ServiceSimple = e.Element.InnerText;
}
else
{
result.ServiceComplex = new ServiceComplex
{
Min_amount = e.Element.SelectSingleNode("min_amount").InnerText,
Max_amount = e.Element.SelectSingleNode("max_amount").InnerText,
Currency = e.Element.SelectSingleNode("currency").InnerText
};
}
}
}
In the event handler, we manually populate the properties of our class from xml.

C# - How to deserialize xml response

I am retrieving xml data from an web api and deserializing the data into objects.:
<Result>
<VendorInfo xml:lang="xx">
<Vendor vname="A" cpe="B">
<Product pname="C" cpe="D"/>
</Vendor>
<Vendor vname="E" cpe="F">
<Product pname="G" cpe="H"/>
</Vendor>
<Vendor vname="I" cpe="J">
<Product pname="K" cpe="L"/>
<Product pname="M" cpe="N"/>
</Vendor>
</VendorInfo>
<Status keyword="hoge" feed="bar"/>
</Result>
My current code is this:
[XmlRoot(ElementName = "Product")]
public class Product
{
[XmlAttribute(AttributeName = "pname")]
public string Pname { get; set; }
[XmlAttribute(AttributeName = "cpe")]
public string Cpe { get; set; }
}
[XmlRoot(ElementName = "Vendor")]
public class Vendor
{
[XmlArrayItem(ElementName = "Product")]
public List<Product> Product { get; set; }
[XmlAttribute(AttributeName = "vname")]
public string Vname { get; set; }
[XmlAttribute(AttributeName = "cpe")]
public string Cpe { get; set; }
}
[XmlRoot(ElementName = "VendorInfo")]
public class VendorInfo
{
[XmlElement(ElementName = "Vendor")]
public List<Vendor> Vendor { get; set; }
[XmlAttribute(AttributeName = "lang")]
public string Lang { get; set; }
}
[XmlRoot(ElementName = "Status")]
public class Status
{
[XmlAttribute(AttributeName = "feed")]
public string Feed { get; set; }
[XmlAttribute(AttributeName = "keyword")]
public string Keyword { get; set; }
}
[XmlRoot(ElementName = "Result")]
public class Result
{
[XmlElement(ElementName = "VendorInfo")]
public VendorInfo VendorInfo { get; set; }
[XmlElement(ElementName = "Status")]
public Status Status { get; set; }
}
But, This code does not working correctly.
Only first 2 Vendor elements are deserialized, Product element is not deserialized.
What am i doing wrong?
best regards
You have few things to be taken care.
Make only one element XmlRoot. Refer below link and search for "Controlling Serialization of Classes Using XmlRootAttribute and XmlTypeAttribute".
https://learn.microsoft.com/en-us/dotnet/standard/serialization/controlling-xml-serialization-using-attributes
Here in your case, the "Result" class is root.
Make rest all XmlType.
In the vendor class instead of "XmlArrayItem", make it just "XmlElement".
Here is the working code.
[XmlType("Product")]
public class Product
{
[XmlAttribute(AttributeName = "pname")]
public string Pname { get; set; }
[XmlAttribute(AttributeName = "cpe")]
public string Cpe { get; set; }
}
[XmlType("Vendor")]
public class Vendor
{
[XmlElement(ElementName = "Product")]
public List<Product> Product { get; set; }
[XmlAttribute(AttributeName = "vname")]
public string Vname { get; set; }
[XmlAttribute(AttributeName = "cpe")]
public string Cpe { get; set; }
}
[XmlType("VendorInfo")]
public class VendorInfo
{
[XmlElement(ElementName = "Vendor")]
public List<Vendor> Vendor { get; set; }
[XmlAttribute(AttributeName = "xml:lang")]
public string Lang { get; set; }
}
[XmlType("Status")]
public class Status
{
[XmlAttribute(AttributeName = "feed")]
public string Feed { get; set; }
[XmlAttribute(AttributeName = "keyword")]
public string Keyword { get; set; }
}
[XmlRoot(ElementName = "Result")]
public class Result
{
[XmlElement(ElementName = "VendorInfo")]
public VendorInfo VendorInfo { get; set; }
[XmlElement(ElementName = "Status")]
public Status Status { get; set; }
}

Deserialize xml xmpp message to object

I'm trying to deserialize a xml string to a c# object. This is the message:
<message from='test1#localhost' to='test2#localhost'><result xmlns='urn:xmpp:mam:tmp' id='A6QV1I4TKO81'><forwarded xmlns='urn:xmpp:forward:0'><delay xmlns='urn:xmpp:delay' from='test1#localhost' stamp='2015-07-21T09:12:09Z'></delay><message type='mchat'><subject/><body/></message></forwarded></result></message>
And this is the class
public class Delay {
[XmlAttribute(AttributeName="xmlns")]
public string Xmlns { get; set; }
[XmlAttribute(AttributeName="from")]
public string From { get; set; }
[XmlAttribute(AttributeName="stamp")]
public string Stamp { get; set; }
}
public class Active {
[XmlAttribute(AttributeName="xmlns")]
public string Xmlns { get; set; }
}
public class XmppMessage {
[XmlElement(ElementName="body")]
public string Body { get; set; }
[XmlAttribute(AttributeName="lang")]
public string Lang { get; set; }
[XmlAttribute(AttributeName="type")]
public string Type { get; set; }
[XmlAttribute(AttributeName="id")]
public string Id { get; set; }
[XmlAttribute(AttributeName="to")]
public string To { get; set; }
}
public class Forwarded {
[XmlElement(ElementName="delay")]
public Delay Delay { get; set; }
[XmlElement(ElementName="message")]
public XmppMessage Message { get; set; }
[XmlAttribute(AttributeName="xmlns")]
public string Xmlns { get; set; }
}
public class Result {
[XmlElement(ElementName="forwarded")]
public Forwarded Forwarded { get; set; }
[XmlAttribute(AttributeName="xmlns")]
public string Xmlns { get; set; }
[XmlAttribute(AttributeName="id")]
public string Id { get; set; }
}
[XmlRoot(ElementName="message")]
public class MessageHistory {
[XmlElement(ElementName="result")]
public Result Result { get; set; }
[XmlAttribute(AttributeName="from")]
public string From { get; set; }
[XmlAttribute(AttributeName="to")]
public string To { get; set; }
}
This is the code to deserialise:
MessageHistory messageNode;
XmlSerializer serializer = new XmlSerializer(typeof(MessageHistory));
using (StringReader reader = new StringReader(message))
{
messageNode = (MessageHistory)(serializer.Deserialize(reader));
}
The object property "from" and "to" are fine but the "Result" is returning null. I can't understand what I'm missing here...
The problem is the namespaces in the XML. you have to specify the namespaces explicitly, like this:
public class Forwarded
{
[XmlElement(ElementName = "delay", Namespace = "urn:xmpp:delay")]
public Delay Delay { get; set; }
[XmlElement(ElementName = "message")]
public XmppMessage Message { get; set; }
[XmlAttribute(AttributeName = "xmlns")]
public string Xmlns { get; set; }
}
public class Result
{
[XmlElement(ElementName = "forwarded", Namespace = "urn:xmpp:forward:0")]
public Forwarded Forwarded { get; set; }
[XmlAttribute(AttributeName = "xmlns")]
public string Xmlns { get; set; }
[XmlAttribute(AttributeName = "id")]
public string Id { get; set; }
}
[XmlRoot(ElementName = "message")]
public class MessageHistory
{
[XmlElement(ElementName = "result", Namespace = "urn:xmpp:mam:tmp")]
public Result Result { get; set; }
[XmlAttribute(AttributeName = "from")]
public string From { get; set; }
[XmlAttribute(AttributeName = "to")]
public string To { get; set; }
}

.NET Xml deserialization, issue/error with xsi:type attribute

VS2008, .NET Framework 3.5
We're utilizing the WebEx Xml API. Here's a sample Xml response from their web service that I'm trying to deserialize into .NET classes.
<?xml version="1.0" encoding="UTF-8"?>
<serv:message xmlns:serv="http://www.webex.com/schemas/2002/06/service" xmlns:com="http://www.webex.com/schemas/2002/06/common"
xmlns:event="http://www.webex.com/schemas/2002/06/service/event"><serv:header><serv:response><serv:result>SUCCESS</serv:result><serv:gsbStatus>PRIMARY</s
erv:gsbStatus></serv:response></serv:header>
<serv:body>
<serv:bodyContent xsi:type="event:lstsummaryEventResponse" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<event:matchingRecords>
<serv:total>2</serv:total>
<serv:returned>2</serv:returned>
<serv:startFrom>1</serv:startFrom>
</event:matchingRecords>
<event:event>
<event:sessionKey>999999</event:sessionKey>
<event:sessionName>Test Event 1</event:sessionName>
<event:sessionType>129</event:sessionType>
<event:hostWebExID>SomeName</event:hostWebExID>
<event:startDate>03/28/2012 14:30:00</event:startDate>
<event:endDate>03/28/2012 14:45:00</event:endDate>
<event:timeZoneID>11</event:timeZoneID>
<event:duration>15</event:duration>
<event:description></event:description>
<event:status>NOT_INPROGRESS</event:status>
<event:panelists></event:panelists>
<event:listStatus>PUBLIC</event:listStatus>
</event:event>
</serv:bodyContent>
</serv:body>
</serv:message>
Here's the class that we're deserializing into:
using System;
using System.Xml;
using System.Xml.Serialization;
using System.Xml.Schema;
using System.Collections.Generic;
namespace Masonite.MTier.WebEx
{
[Serializable()]
[XmlRoot("message", Namespace = "http://www.webex.com/schemas/2002/06/service")]
public class lstsummaryEventResponsexx
{
[XmlNamespaceDeclarations]
public XmlSerializerNamespaces xmlns = new XmlSerializerNamespaces();
public lstsummaryEventResponsexx()
{
xmlns.Add("serv", "http://www.webex.com/schemas/2002/06/service");
xmlns.Add("com", "http://www.webex.com/schemas/2002/06/common");
xmlns.Add("event", "http://www.webex.com/schemas/2002/06/service/event");
}
[XmlElement(ElementName = "header")]
public Header header { get; set; }
[XmlElement(ElementName = "body")]
public Body body { get; set; }
[Serializable()]
[XmlRoot("header")]
public class Header
{
[XmlElement(ElementName = "response")]
public Response response { get; set; }
}
[Serializable()]
[XmlRoot("body")]
[XmlInclude(typeof(lstsummaryEventResponse))]
public class Body
{
[XmlElement(ElementName = "bodyContent", Form = XmlSchemaForm.Qualified)]
public BodyContent bodyContent { get; set; }
}
[Serializable()]
public class lstsummaryEventResponse
{
}
[Serializable()]
[XmlRoot("response")]
public class Response
{
[XmlElement(ElementName = "result")]
public string result { get; set; }
[XmlElement(ElementName = "reason")]
public string reason { get; set; }
[XmlElement(ElementName = "gsbStatus")]
public string gsbStatus { get; set; }
[XmlElement(ElementName = "exceptionID")]
public string exceptionID { get; set; }
}
[Serializable()]
[XmlRoot("bodyContent")]
public class BodyContent
{
[XmlElement(ElementName = "matchingRecords", Namespace = "http://www.webex.com/schemas/2002/06/service/event")]
public MatchingRecords matchingRecords { get; set; }
[XmlElement(ElementName = "event", Namespace = "http://www.webex.com/schemas/2002/06/service/event")]
public List<EventSummary> events { get; set; }
}
[Serializable()]
[XmlRoot("matchingRecords")]
public class MatchingRecords
{
[XmlElement(ElementName = "total", Namespace = "http://www.webex.com/schemas/2002/06/service")]
public int total { get; set; }
[XmlElement(ElementName = "returned", Namespace = "http://www.webex.com/schemas/2002/06/service")]
public int returned { get; set; }
[XmlElement(ElementName = "startFrom", Namespace = "http://www.webex.com/schemas/2002/06/service")]
public int startFrom { get; set; }
}
[Serializable()]
[XmlRoot("event")]
public class EventSummary
{
[XmlElement(ElementName = "sessionKey")]
public long sessionKey { get; set; }
[XmlElement(ElementName = "sessionName")]
public string sessionName { get; set; }
[XmlElement(ElementName = "sessionType")]
public int sessionType { get; set; }
[XmlElement(ElementName = "hostWebExID")]
public string hostWebExID { get; set; }
[XmlElement(ElementName = "startDate")]
public string startDate { get; set; }
[XmlElement(ElementName = "endDate")]
public string endDate { get; set; }
[XmlElement(ElementName = "timeZoneID")]
public int timeZoneID { get; set; }
[XmlElement(ElementName = "duration")]
public int duration { get; set; }
[XmlElement(ElementName = "description")]
public string description { get; set; }
[XmlElement(ElementName = "status")]
public string status { get; set; }
[XmlElement(ElementName = "panelists")]
public string panelists { get; set; }
[XmlElement(ElementName = "listStatus")]
public listingType listStatus { get; set; }
}
}
}
The error I'm receiving:
The specified type was not recognized: name='lstsummaryEventResponse', namespace='http://www.webex.com/schemas/2002/06/service/event', at <bodyContent xmlns='http://www.webex.com/schemas/2002/06/service'>
I'm not sure how to provide the type lstsummaryEventResponse for the Deserialize method. I added another serializable class to my class above using that name, but get the same error. Any thoughts?
BodyContent can have the type event:lstsummaryEventResponse - so you have to declare the corresponding class, and then decorate the declaration of BodyContent as follows:
[Serializable()]
[XmlRoot("bodyContent")]
[XmlInclude("lstsummaryEventResponse")]
public class BodyContent {
}
Having said that, creating C# class with a serialization corresponding to some arbitrary XML is pretty tricky, I am not sure it is right approach

Categories