0

I am writing a REST API that simply gets a response for a url. However the challenging part is that the response is in xml format instead of json. I am not sure how to parse this xml and get specific values such as Status, Facility Description (all inside the event tag) and Ship Date. Here is what I have so far but i am pretty stuck. Thank you for the help.

Sample XML response:

<ShipmentStatus xmlns:xsi="http://www.w3.org/2001/XMLSchema- instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Packages> 
   <Package>
      <Events> 
        <Event>
           <Status>AA</Status>
           <Description>DATA ENTRY</Description> 
           <EventTime>2014-03-13T10:48:05.14</EventTime> 
           <Facility>Commerce</Facility> 
           <City>COMMERCE</City>
           <State>CA</State>
           <Zip>90040</Zip>
        </Event>
      </Events>
      <Tracking>C11421500000014</Tracking> 
      <Exp_Del_Date>2014-03-14T00:00:00</Exp_Del_Date> 
      <ShipDate>2014-03-13T00:00:00</ShipDate> 
      <Delivered>false</Delivered>
      <Zip>90210</Zip>
      <Service>S</Service>
      <POD/>
      <Error/>
   </Package> 
</Packages>
<Note>Results Provided by OnTrac at 3/13/2014 10:48 AM</Note>
<Error/> 
</ShipmentStatus>

My attempt at REST API:

Http h = new Http();
        HttpRequest req = new HttpRequest();
        // url that returns the XML in the response body
        TrackingNumber ='123456';
        req.setEndpoint('https://www.shippingstatus.net/ShippingServices.svc/V5/shipments?tn=' + TrackingNumber);
        req.setMethod('GET');
        HttpResponse res = h.send(req);
        Dom.Document doc = res.getBodyDocument();
        
        //Retrieve the root element for this document.
        Dom.XMLNode packages = doc.getRootElement(); //is packages the root element?
        
        for(Dom.XMLNode child : packages.getChildElements()) {
           System.debug(child.getText());  //but how do I get specific tag values like status, facility, description etc???
        }

Latest Attempt:

public class TracProcess {
    
    public class trackNumberInput {
        @InvocableVariable(required=true)
        public String trackingNumber;
        
    }
    
    
    public void getTrackingStatus(String trackingNumber){
        Http h = new Http();
        HttpRequest req = new HttpRequest();
        // url that returns the XML in the response body
        req.setEndpoint('https://www.somewebsiteurl.com/WebServices/V1/shipments?tn='+trackingNumber);
        req.setMethod('GET');
        HttpResponse res = h.send(req);
        Dom.Document doc = res.getBodyDocument();
        
        //Retrieve the root element for this document.
        Dom.XMLNode root = doc.getRootElement();
        
//How do i call the classes that I have created in this web service?
        

    }
    
    public class ShipmentStatus{
    Packages packages;
    Note note;
    Error error;

    public void deserialize(Dom.XmlNode node){
        // more looping
        for(Dom.XmlNode child :node.getChildren()){
            switch on child.getName(){
                when 'Packages'{
                    packages = new Packages();
                    packages.deserialize(child);
                }
                when 'Note'{
                    note = new Note();
                    note.deserialize(child);
                }
                when 'Error'{
                    error = new Error();
                    error.deserialize(child);
                }
            }
        }
    }
    }
    
    public class Packages{
        Package package; //keeps on showing error on this because it is a reserved identifier? how do i fix this?
        
        public void deserialize(Dom.XmlNode node){
            for(Dom.XmlNode child :node.getChildren()){
                if(child.getName() == 'Package'){
                    package = new Package();

                    package.deserialize(child);
                }
            }
        }           
    }
    
    public class Package{
        Events events;
        Tracking tracking;
        Exp_Del_Date expDelDate;
        ShipDate sd;
        Delivered delivered;
        Zip zip;
        Service service;
        POD pod;
        Error error;
        
       

    public void deserialize(Dom.XmlNode node){
        
        for(Dom.XmlNode child :node.getChildren()){
            switch on child.getName(){
                when 'Events'{
                    events = new Events();
                    events.deserialize(child);
                }
                when 'Tracking'{
                    tracking = new Tracking();
                    tracking.deserialize(child);
                }
                when 'Exp_Del_Date'{
                    expDelDate = new Exp_Del_Date();
                    expDelDate.deserialize(child);
                }
                when 'ShipDate'{
                    sd = new ShipDate();
                    expDelDate.deserialize(child);
                }
                when 'Delivered'{
                    delivered = new Delivered();
                    delivered.deserialize(child);
                }
                when 'Zip'{
                    zip = new Zip();
                    zip.deserialize(child);
                }
                when 'Service'{
                    service = new Service();
                    service.deserialize(child);
                }
                when 'POD'{
                    pod = new POD();
                    pod.deserialize(child);
                }
                when 'Error'{
                    error = new Error();
                    error.deserialize(child);
                }
            }
        }
    }
}
    
    public class Events{
    // When we get to items that only contain primitive values, it's time
    //  to just extract them directly
        String Event;
        String Status;
        String Description; //should this be string?
        DateTime EventTime;
        String Facility;
        String City;
        String State;
        String Zip;
        
        public void deserialize(Dom.XmlNode node){
            for(Dom.XmlNode child :node.getChildren()){
                switch on child.getName(){
                    when 'Event'{
                        Event = (String)child.getValue();
                    }
                    when 'Status'{
                        Status = (String)child.getValue();
                    }
                    when 'Description'{
                        Description = (String)child.getValue();
                    }
                    when 'EventTime'{
                        EventTime = (DateTime)child.getValue();
                    }
                    when 'Facility'{
                        Facility = (String)child.getValue();
                    }
                    when 'City'{
                        City = (String)child.getValue();
                    }
                    when 'State'{
                        State = (String)child.getValue();
                    }
                    when 'Zip'{
                        Zip = (String)child.getValue();
                    }
                }
            }
        }
    }
    
    
    
}
4
  • I am still unsure about this. So once I write a "parser" class then how does the REST API call it? Also, do I have to put the xml response as a string in the code? Aug 13, 2022 at 15:45
  • 2
    With what you've shown us so far, you don't have a REST API. Instead, you're making a callout to a REST API. At the very bottom of the example that I provided in the linked question, there is a example of usage. Basically, you create an instance of the outermost class, and then call deserialize() and pass the parsed root element into that method.
    – Derek F
    Aug 13, 2022 at 15:56
  • Apologize for the mistake in terminology. You are right I am calling a REST API and the apex just retrieves/gets the response and parses. I am going to attempt the code you provided and paste my attempt here today because I am still unsure. Thank you Aug 13, 2022 at 16:48
  • @DerekF Here is my latest attempt. I am not sure if this is correct. I am not sure how to write the class for tags such as Tracking/ShipDate etc. Also, once this is parsed how do i convert this as output so I can pass those values in the web service? Aug 14, 2022 at 4:11

1 Answer 1

1

How do you call the deserialization classes?

The same way you call any other method, really.

  • Make an instance of the class
  • Call the method and pass arguments

That's really all there is to it

public void getTrackingStatus(String trackingNumber){
        Http h = new Http();
        HttpRequest req = new HttpRequest();
        // url that returns the XML in the response body
        req.setEndpoint('https://www.somewebsiteurl.com/WebServices/V1/shipments?tn='+trackingNumber);
        req.setMethod('GET');
        HttpResponse res = h.send(req);
        Dom.Document doc = res.getBodyDocument();
        
        //Retrieve the root element for this document.
        Dom.XMLNode root = doc.getRootElement();

        // Create an instance of the class
        ShipmentStatus status = new ShipmentStatus();

        // Call the method
        status.deserialize(root);
}

After you get the root XML Node into the top-level deserialization class, the work that you did in the deserialization method ensures that the rest of the work just happens.

How do I work around reserved keywords?

Since you're in control of the classes you're using for deserialization, you can simply rename problematic items

So instead of Package, you could use xPackage or Pkg instead

public class Packages{
        // "package" is problematic, so just use another name
        Pkg myPkg;
        
        public void deserialize(Dom.XmlNode node){
            for(Dom.XmlNode child :node.getChildren()){
                if(child.getName() == 'Package'){
                    myPkg = new Pkg();

                    package.deserialize(child);
                }
            }
        }           
    }

    public class Pkg{
        Events events;
        Tracking tracking;
        Exp_Del_Date expDelDate;
        ShipDate sd;
        Delivered delivered;
        Zip zip;
        Service service;
        POD pod;
        Error error;

What do I do with [piece of data]?

The safe approach is to just treat it as a string

If you don't care about a particular piece of data, you could just simply ignore it.

e.g. instead of

when 'Description'{
    Description = (String)child.getValue();
}

you could just do nothing

when 'Description'{
}

or possibly remove the entire "when" block (though you may need to have a when else {} to compensate)

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .