Home > front end >  Deserializing XML output from an API response
Deserializing XML output from an API response

Time:01-07

This is the first time I have successfully written code to retrieve XML data from an API call which I then store in a string. I now need to parse that XML string into a class structure in order that I can use it in other parts of my code. This is where I am running into an issue.

The XML string returned from the API is as follows:

<?xml version="1.0" encoding="UTF-8"?>
<OTA_AirDetailsRS
 PrimaryLangID="eng"
 Version="1.0"
 TransactionIdentifier=""
 FLSNote="This XML adds attributes not in the OTA XML spec.  All such attributes start with FLS"
 FLSDevice = "ota-xml-expanded"
 xmlns="http://www.opentravel.org/OTA/2003/05">
 <Success></Success>
 <FLSResponseFields
   FLSOriginCode="LHR"
   FLSOriginName="Heathrow Airport"
   FLSDestinationCode="MAD"
   FLSDestinationName="Madrid"
   FLSStartDate="2023-01-18"
   FLSEndDate="2023-01-18"
   FLSResultCount="35"
   FLSRoutesFound="155"
   FLSBranchCount="3805"
   FLSTargetCount="2093"
   FLSRecordCount="1280181"
 />
 <FlightDetails 
   TotalFlightTime="PT2H35M"
   TotalMiles="775"
   TotalTripTime="PT2H35M"
   FLSDepartureDateTime="2023-01-18T06:20:00"
   FLSDepartureTimeOffset=" 0000"
   FLSDepartureCode="LHR"
   FLSDepartureName="Heathrow Airport"
   FLSArrivalDateTime="2023-01-18T09:55:00"
   FLSArrivalTimeOffset=" 0100"
   FLSArrivalCode="MAD"
   FLSArrivalName="Madrid"
   FLSFlightType="NonStop"
   FLSFlightLegs="1"
   FLSFlightDays="..3...."
   FLSDayIndicator="">
   <FlightLegDetails
    DepartureDateTime="2023-01-18T06:20:00"
    FLSDepartureTimeOffset=" 0000"
    ArrivalDateTime="2023-01-18T09:55:00"
    FLSArrivalTimeOffset=" 0100"
    FlightNumber="456"
    JourneyDuration="PT2H35M"
    SequenceNumber="1"
    LegDistance="775"
    FLSMeals="G"
    FLSInflightServices=" "
    FLSUUID="LHRMAD20230118BA456"
   >
   <DepartureAirport 
    CodeContext="IATA" 
    LocationCode="LHR" 
    FLSLocationName="Heathrow Airport" 
    Terminal="3" 
    FLSDayIndicator="" 
   />
   <ArrivalAirport 
    CodeContext="IATA" 
    LocationCode="MAD" 
    FLSLocationName="Adolfo Suarez-Barajas Airport" 
    Terminal="4S" 
    FLSDayIndicator="" 
   />
   <MarketingAirline 
    Code="BA" 
    CodeContext="IATA" 
    CompanyShortName="British Airways" 
   />
   <Equipment 
    AirEquipType="320" 
   />
   </FlightLegDetails>
 </FlightDetails> 
</OTA_AirDetailsRS>

There could be any number of 'FlightDetails' elements depending on how many flights are returned from the API. Similarly there could be any number of 'FlightLegDetails' elements depending on whether each flight is direct or has a number of stops (denoted by the 'FLSFlightType' attribute under each 'FlightDetails' element.

The class structure I have written is as follows:

public class FlightSearchResults
    {
        public class searchResults
        {
            [XmlElement("OTA_AirDetailsRS")]
            public List<OTA_AirDetailsRS> OTA_AirDetailsRS { get; set; }
            [XmlAttribute("PrimaryLangID")]
            public List<success> success { get; set; }
            [XmlElement("ResponseFields")]
            public List<responseFields> ResponseFields { get; set; }
            [XmlElement("FlightDetails")]
            public List<flightDetails> FlightDetails { get; set; }
        }

        public class OTA_AirDetailsRS
        {
            [XmlAttribute("PrimaryLangID")]
            public string PrimaryLangID { get; set; }
            [XmlAttribute("Version")]
            public string Version { get; set; }
            [XmlAttribute("TransactionIdentifier")]
            public string TransactionIdentifier { get; set; }
            [XmlAttribute("FLSNote")]
            public string FLSNote { get; set; }
            [XmlAttribute("FLSDevice")]
            public string FLSDevice { get; set; }
            [XmlAttribute("xmlns")]
            public string xmlns { get; set; }
        }

        public class success
        {
            [XmlElement("Success")]
            public string SuccessIndicator { get; set; }
        }

        public class responseFields
        {
            [XmlAttribute("FLSOriginCode")]
            public string FLSOriginCode { get; set; }
            [XmlAttribute("FLSOriginName")]
            public string FLSOriginName { get; set; }
            [XmlAttribute("FLSDestinationCode")]
            public string FLSDestinationCode { get; set; }
            [XmlAttribute("FLSDestinationName")]
            public string FLSDestinationName { get; set; }
            [XmlAttribute("FLSStartDate")]
            public string FLSStartDate { get; set; }
            [XmlAttribute("FLSEndDate")]
            public string FLSEndDate { get; set; }
            [XmlAttribute("FLSResultCount")]
            public string FLSResultCount { get; set; }
            [XmlAttribute("FLSRoutesFound")]
            public string FLSRoutesFound { get; set; }
            [XmlAttribute("FLSBranchCount")]
            public string FLSBranchCount { get; set; }
            [XmlAttribute("FLSTargetCount")]
            public string FLSTargetCount { get; set; }
            [XmlAttribute("FLSRecordCount")]
            public string FLSRecordCount { get; set; }
        }

        public class flightDetails
        {
            [XmlAttribute("TotalFlightTime")]
            public string TotalFlightTime { get; set; }
            [XmlAttribute("TotalMiles")]
            public string TotalMiles { get; set; }
            [XmlAttribute("TotalTripTime")]
            public string TotalTripTime { get; set; }
            [XmlAttribute("FLSDepartureDateTime")]
            public string FLSDepartureDateTime { get; set; }
            [XmlAttribute("FLSDepartureTimeOffset")]
            public string FLSDepartureTimeOffset { get; set; }
            [XmlAttribute("DepartureCode")]
            public string FLSDepartureCode { get; set; }
            [XmlAttribute("FLSDepartureName")]
            public string FLSDepartureName { get; set; }
            [XmlAttribute("FLSArrivalDateTime")]
            public string FLSArrivalDateTime { get; set; }
            [XmlAttribute("FLSArrivalTimeOffset")]
            public string FLSArrivalTimeOffset { get; set; }
            [XmlAttribute("FLSArrivalCode")]
            public string FLSArrivalCode { get; set; }
            [XmlAttribute("FLSArrivalName")]
            public string FLSArrivalName { get; set; }
            [XmlAttribute("FLSFlightType")]
            public string FLSFlightType { get; set; }
            [XmlAttribute("FLSFlightLegs")]
            public string FLSFlightLegs { get; set; }
            [XmlAttribute("FLSFlightDays")]
            public string FLSFlightDays { get; set; }
            [XmlAttribute("FLSDayIndicator")]
            public string FLSDayIndicator { get; set; }
            [XmlElement("FlightLegDetails")]
            public List<flightLegDetails> FlightLegDetails { get; set; }
        }

        public class flightLegDetails
        {
            [XmlAttribute("DepartureDateTime")]
            public string DepartureDateTime { get; set; }
            [XmlAttribute("FLSDepartureTimeOffset")]
            public string FLSDepartureTimeOffset{ get; set; }
            [XmlAttribute("ArrivalDateTime")]
            public string ArrivalDateTime { get; set; }
            [XmlAttribute("ArrivalTimeOffset")]
            public string ArrivalTimeOffset { get; set; }
            [XmlAttribute("FlightNumber")]
            public string FlightNumber { get; set; }
            [XmlAttribute("JourneyDuration")]
            public string JourneyDuration  { get; set; }
            [XmlAttribute("SequenceNumber")]
            public string SequenceNumber { get; set; }
            [XmlAttribute("LegDistance")]
            public string LegDistance { get; set; }
            [XmlAttribute("FLSMeals")]
            public string FLSMeals { get; set; }
            [XmlAttribute("FLSInflightServices")]
            public string FLSInflightServices { get; set; }
            [XmlAttribute("FLSUUND")]
            public string FLSUUID { get; set; }
            [XmlElement("DepartureAirport")]
            public List<departureAirport> DepartureAirport { get; set; }
            [XmlElement("ArrivalAirport")]
            public List<arrivalAirport> ArrivalAirport { get; set; }
            [XmlElement("MarketingAirline")]
            public List<marketingAirline> MarketingAirline { get; set; }
            [XmlAttribute("AirEquipType")]
            public string AirEquipType { get; set; }
        }

        public class departureAirport
        {
            [XmlAttribute("CodeContext")]
            public string CodeContext { get; set; }
            [XmlAttribute("LocationCode")]
            public string LocationCode { get; set; }
            [XmlAttribute("FLSLocationName")]
            public string FLSLocationName { get; set; }
            [XmlAttribute("Terminal")]
            public string Terminal { get; set; }
            [XmlAttribute("FLSDayIndicator")]
            public string FLSDayIndicator { get; set; }
        }
        public class arrivalAirport
        {
            [XmlAttribute("CodeContext")]
            public string CodeContext { get; set; }
            [XmlAttribute("LocationCode")]
            public string LocationCode { get; set; }
            [XmlAttribute("FLSLocationName")]
            public string FLSLocationName { get; set; }
            [XmlAttribute("Terminal")]
            public string Terminal { get; set; }
            [XmlAttribute("FLSDayIndicator")]
            public string FLSDayIndicator { get; set; }
        }

        public class marketingAirline
        {
            [XmlAttribute("Code")]
            public string Code { get; set; }
            [XmlAttribute("CodeContext")]
            public string CodeContext { get; set; }
            [XmlAttribute("CompanyShortName")]
            public string CompanyShortName { get; set; }
        }
 
        public static List<FlightSearchResults> flightSearchRes = new List<FlightSearchResults>();
    }

And the code to parse the XML string is:

public static object XmlDeserializeFromString(this string objectData, Type type)
        {
            var serializer = new XmlSerializer(type);
            object result;

            using (TextReader reader = new StringReader(objectData))
            {
                result = serializer.Deserialize(reader);
            }

            return result;
        }

which I call via:

FlightSearchResults.flightSearchRes = (List<FlightSearchResults>)Functions.XmlDeserializeFromString(xmlRes, xmlRes.GetType());

The error occurs on the 'result = serializer.Deserialize(reader);' line of the XmlDeserializeFromString function - '<OTA_AirDetailsRS xmlns='http://www.opentravel.org/OTA/2003/05'> was not expected.'. Full error details below:

System.InvalidOperationException
  HResult=0x80131509
  Message=There is an error in XML document (2, 2).
  Source=System.Xml
  StackTrace:
   at System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader, String encodingStyle, XmlDeserializationEvents events)
   at System.Xml.Serialization.XmlSerializer.Deserialize(TextReader textReader)
   at World_Airport_Details.Functions.XmlDeserializeFromString(String objectData, Type type) in C:\Users\Chris\source\repos\World Airport Details\World Airport Details\Functions.cs:line 600
   at World_Airport_Details.DetailsWindow.btnSearchFlights_Click(Object sender, RoutedEventArgs e) in C:\Users\Chris\source\repos\World Airport Details\World Airport Details\DetailsWindow.xaml.cs:line 5455
   at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
   at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
   at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
   at System.Windows.UIElement.RaiseEvent(RoutedEventArgs e)
   at System.Windows.Controls.Primitives.ButtonBase.OnClick()
   at System.Windows.Controls.Button.OnClick()
   at System.Windows.Controls.Primitives.ButtonBase.OnMouseLeftButtonUp(MouseButtonEventArgs e)
   at System.Windows.UIElement.OnMouseLeftButtonUpThunk(Object sender, MouseButtonEventArgs e)
   at System.Windows.Input.MouseButtonEventArgs.InvokeEventHandler(Delegate genericHandler, Object genericTarget)
   at System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target)
   at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
   at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
   at System.Windows.UIElement.ReRaiseEventAs(DependencyObject sender, RoutedEventArgs args, RoutedEvent newEvent)
   at System.Windows.UIElement.OnMouseUpThunk(Object sender, MouseButtonEventArgs e)
   at System.Windows.Input.MouseButtonEventArgs.InvokeEventHandler(Delegate genericHandler, Object genericTarget)
   at System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target)
   at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs)
   at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised)
   at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args)
   at System.Windows.UIElement.RaiseTrustedEvent(RoutedEventArgs args)
   at System.Windows.UIElement.RaiseEvent(RoutedEventArgs args, Boolean trusted)
   at System.Windows.Input.InputManager.ProcessStagingArea()
   at System.Windows.Input.InputManager.ProcessInput(InputEventArgs input)
   at System.Windows.Input.InputProviderSite.ReportInput(InputReport inputReport)
   at System.Windows.Interop.HwndMouseInputProvider.ReportInput(IntPtr hwnd, InputMode mode, Int32 timestamp, RawMouseActions actions, Int32 x, Int32 y, Int32 wheel)
   at System.Windows.Interop.HwndMouseInputProvider.FilterMessage(IntPtr hwnd, WindowMessage msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at System.Windows.Interop.HwndSource.InputFilterMessage(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
   at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
   at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
   at System.Windows.Threading.Dispatcher.LegacyInvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
   at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
   at MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
   at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
   at System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
   at System.Windows.Window.ShowHelper(Object booleanBox)
   at System.Windows.Window.Show()
   at System.Windows.Window.ShowDialog()

  This exception was originally thrown at this call stack:
    [External Code]

Inner Exception 1:
InvalidOperationException: <OTA_AirDetailsRS xmlns='http://www.opentravel.org/OTA/2003/05'> was not expected.

Can anyone help me with this as all google searches I have made have not been successful.

Many thanks Chris

  1. Tried moving the xmlns line to line 2 of the XML string before parsing
  2. Tried adding [XmlIgnore] before the 'xmlns' property in the class structure
  3. Tried adding an [XmlRoot] of 'OTA_AirDetailsRS' to the top of the class structure

CodePudding user response:

A few points of interest before we proceed:

(1) There should be exactly ONE root element

This is the XML standard:

"There is exactly one element, called the root, or document element, no part of which appears in the content of any other element".

In your case, that would be OTA_AirDetailsRS. It will be your top level class, and you should never get a List<OTA_AirDetailsRS> from a single valid XML.

(2) xmlns is a namespace.

There is a rather long reading about namespaces in the XML standard. But in short, just like namespaces in your C# code, it is there to ensure uniqueness.
It's not treated like a normal xml attribute.

(3) In C#, an XML attribute/element defaults to the name of the property/field.

I.e. instead of writing [XmlAttribute("Version")] string Version; you could simply do [XmlAttribute] string Version;. Unless you name them differently in your class vs in the XML.

* Also note, you may have to review where you are using List<>. E.g. does it really make sense to have multiple DepartureAirport on a single leg?


Now that that's out of the way, the following fragment works against your XML:

public static void Main() {
  var airDetails = DeserializeFromString<OTA_AirDetailsRS>(yourXmlStringHere);
  // .. do stuff
}

public static T DeserializeFromString<T>(string xml) {
  // You need deserialize your ROOT class: OTA_AirDetailsRS
  var serializer = new XmlSerializer(typeof(T));
  using var reader = new StringReader(xml);
  return (T)serializer.Deserialize(reader);
}

// This is your root. You specify it as an `XmlRoot()`. 
// `xmlns` should go here as `Namespace`.
[XmlRoot(Namespace = "http://www.opentravel.org/OTA/2003/05")]
public class OTA_AirDetailsRS {
  [XmlAttribute] public string PrimaryLangID { get; set; }
  [XmlAttribute] public string Version { get; set; }
  [XmlAttribute] public string TransactionIdentifier { get; set; }
  [XmlAttribute] public string FLSNote { get; set; }
  [XmlAttribute] public string FLSDevice { get; set; }
  [XmlElement]   public responseFields FLSResponseFields { get; set; }
  [XmlElement]   public List<flightDetails> FlightDetails { get; set; }
}


// ----- The rest of these are nothing special -----

public class success {
  [XmlElement] public string SuccessIndicator { get; set; }
}

public class responseFields {
  [XmlAttribute] public string FLSOriginCode { get; set; }
  [XmlAttribute] public string FLSOriginName { get; set; }
  [XmlAttribute] public string FLSDestinationCode { get; set; }
  [XmlAttribute] public string FLSDestinationName { get; set; }
  [XmlAttribute] public string FLSStartDate { get; set; }
  [XmlAttribute] public string FLSEndDate { get; set; }
  [XmlAttribute] public string FLSResultCount { get; set; }
  [XmlAttribute] public string FLSRoutesFound { get; set; }
  [XmlAttribute] public string FLSBranchCount { get; set; }
  [XmlAttribute] public string FLSTargetCount { get; set; }
  [XmlAttribute] public string FLSRecordCount { get; set; }
}

public class flightDetails {
  [XmlAttribute] public string TotalFlightTime { get; set; }
  [XmlAttribute] public string TotalMiles { get; set; }
  [XmlAttribute] public string TotalTripTime { get; set; }
  [XmlAttribute] public string FLSDepartureDateTime { get; set; }
  [XmlAttribute] public string FLSDepartureTimeOffset { get; set; }
  [XmlAttribute] public string FLSDepartureCode { get; set; }
  [XmlAttribute] public string FLSDepartureName { get; set; }
  [XmlAttribute] public string FLSArrivalDateTime { get; set; }
  [XmlAttribute] public string FLSArrivalTimeOffset { get; set; }
  [XmlAttribute] public string FLSArrivalCode { get; set; }
  [XmlAttribute] public string FLSArrivalName { get; set; }
  [XmlAttribute] public string FLSFlightType { get; set; }
  [XmlAttribute] public string FLSFlightLegs { get; set; }
  [XmlAttribute] public string FLSFlightDays { get; set; }
  [XmlAttribute] public string FLSDayIndicator { get; set; }
  [XmlElement] public List<flightLegDetails> FlightLegDetails { get; set; }
}

public class flightLegDetails {
  [XmlAttribute] public string DepartureDateTime { get; set; }
  [XmlAttribute] public string FLSDepartureTimeOffset { get; set; }
  [XmlAttribute] public string ArrivalDateTime { get; set; }
  [XmlAttribute] public string FLSArrivalTimeOffset { get; set; }
  [XmlAttribute] public string FlightNumber { get; set; }
  [XmlAttribute] public string JourneyDuration { get; set; }
  [XmlAttribute] public string SequenceNumber { get; set; }
  [XmlAttribute] public string LegDistance { get; set; }
  [XmlAttribute] public string FLSMeals { get; set; }
  [XmlAttribute] public string FLSInflightServices { get; set; }
  [XmlAttribute] public string FLSUUID { get; set; }
  [XmlElement] public List<departureAirport> DepartureAirport { get; set; }
  [XmlElement] public List<arrivalAirport> ArrivalAirport { get; set; }
  [XmlElement] public List<marketingAirline> MarketingAirline { get; set; }
  [XmlAttribute] public string AirEquipType { get; set; }
}

public class departureAirport {
  [XmlAttribute] public string CodeContext { get; set; }
  [XmlAttribute] public string LocationCode { get; set; }
  [XmlAttribute] public string FLSLocationName { get; set; }
  [XmlAttribute] public string Terminal { get; set; }
  [XmlAttribute] public string FLSDayIndicator { get; set; }
}
public class arrivalAirport {
  [XmlAttribute] public string CodeContext { get; set; }
  [XmlAttribute] public string LocationCode { get; set; }
  [XmlAttribute] public string FLSLocationName { get; set; }
  [XmlAttribute] public string Terminal { get; set; }
  [XmlAttribute] public string FLSDayIndicator { get; set; }
}

public class marketingAirline {
  [XmlAttribute] public string Code { get; set; }
  [XmlAttribute] public string CodeContext { get; set; }
  [XmlAttribute] public string CompanyShortName { get; set; }
}
  • Related