0

I have created a rest API(apex class) in Salesforce. I have multiple nested tags in xml that comes via API request. I am wondering how do I define my post method in apex that could handle the request. Do I have to list all the fields as parameters in the method or is there a way to use DOMElement as a parameter while defining the method and then I go about extracting the data out of it.

XML Data via API:

<TestContact xmlns:xsd="http://www.w3.org/2001/XMLSchema” 
xmlns:xsl="http://www.w3.org/2001/XMLSchema-instance"> 

<VendorData> 
  <CompanyAffiliation>Test</CompanyAffiliation >  *MUST HAVE *
  <Vendor>Test Vendor</Vendor>  * MUST HAVE *
  <VendorID>xyz1234567</VendorID> * MUST HAVE * 
</VendorData>

<Contact>
  <FirstName>TestFirstName</FirstName> * MUST HAVE *  Lead
  <LastName>TestLastName</LastName> * MUST HAVE * Lead

</contact>



</TestContact> 

Rest API in Salesforce:(Approach 1)

@RestResource(urlMapping='/api/*/createContact')
global with sharing class RESTAPI_JSON_XML 
{
    @HttpPost 
    // Is this the right way to define input parameters??

    global static void doPost(String CompanyAffiliation, String Vendor, String VendorId, String FirstName, String Lastname  ) {

    RestResponse standardResp = RestContext.response;
    REST_API_Helper.ResponseBase  CustomeResponse = new 
    REST_API_Helper.ResponseBase(); 
    ContactDetail reqBodyObj = Detail; 

    RestRequest req = RestContext.request;       
    String ContentType = RestContext.request.headers.get('Content-Type') ;

    // I don't know how to parse it and put it into appropriate objects

    Vendor__c vendor= new Vendor__c();
    vendor.Company_Affiliation__c=reqBodyObj.CompanyAffiliation;
    vendor.Vendor__c=reqBodyObj.Vendor;
    vendor.VendorID__c=reqBodyObj.VendorID__c;
    insert vendor;

    Contact con = new Contact();
    con.FirstName = reqBodyObj.FirstName.trim();   
    con.LastName  = reqBodyObj.LastName.trim();
    insert cont;

    CustomeResponse.Success = true;
    CustomeResponse.Message = 'Contact Created id ='+cont.id;
    standardResp.statusCode = 200;
    standardResp.responseBody = 
    REST_API_Helper.formatResponse(CustomeResponse,ContentType); 

   }


}

Rest API in Salesforce:(Approach 2)

@RestResource(urlMapping='/api/*/createContact')
global with sharing class RESTAPI_JSON_XML 
{
    @HttpPost 
    // Is there a way to define one input parameter that captures all the incoming information and then extract it in the body??

    global static void doPost(XMLDOMElement Detail  ) {

     ........=   Detail.CompanyAffiliation;
     ........=   Detail.FirstName;

    }

}

What is the best approach to define the post method from the above two approaches and how can I parse the information using the suggested approach(any parsing/data extracting methods)? Any sample code?

3
  • you want to parse the xml which comes as part of this rest response? May 8, 2019 at 0:25
  • No the request that comes in is in XML format. I might need to return the response in Xml too but have not decided yet. Right now I am struggling with parsing the incoming xml data.
    – Student
    May 8, 2019 at 0:27
  • have you checked the xmlnode class? May 8, 2019 at 0:31

3 Answers 3

1

Do I have to list all the fields as parameters in the method or is there a way to use DOMElement as a parameter while defining the method and then I go about extracting the data out of it.

One Approach is like declare your http post method with no parameters and when you pass the XML request body it will be copied in the RestRequest.requestBody as Blob.Then you can convert to string and pass it Dom.Document.load() something like below and parse it.

  @RestResource(urlMapping='/api/*/createContact')
    global with sharing class RESTAPI_JSON_XML{
      @httppost
        global static void doPost(){
            RestRequest req = RestContext.request;
            system.debug('XML String---'+req.requestBody.toString());
            DOM.Document doc=new DOM.Document();
            doc.load(req.requestBody.toString());
            DOM.XmlNode rootNode=doc.getRootElement();
            //XML Parsing    
        }
     }
1

parsing/deserializing

Dealing with XML in Apex is a pain. The best approach that I've found so far is to use the classes in the Dom namespace, Dom.Document and DOM.XmlNode, in combination with a class structure that mimics the schema of your XML.

Think of it like creating the class structure that you'd need to deserialize the data if it were JSON. The difference with XML is that you'd need to add some logic to take a Dom.XmlNode and extract the data into class variables yourself.

Your flow here is then: receive request -> use Dom.Document.load() to do the parsing -> send Dom.XmlNodes into the "deserialization" classes to make it easy to use the data in Apex.

A search on XML answers that I've given should help out, but in interest of not just saying "here's the link, go there", here's a reproduction of one of the example "deserialization" class(es) that I've given.

public class Permissions{
    // 'Permission' is an inner class, which is defined below.
    // I find it helpful to have class names and variable names mirror the names used
    //   in the XML document as closely as possible.
    public List<Permission> userPermissions;

    public Permissions(String xmlInput){
        Dom.Document inputDoc = new Dom.Document();
        inputDoc.load(xmlInput);

        process(inputDoc.getRootElement());
    }

    // If you're going to use this in a visualforce page, you'll need to manually
    //   implement getter methods.
    public List<Permission> getUserPermissions(){
        return userPermissions;
    }

    // Each inner class will need a 'process' method like this one.
    private void process(Dom.XmlNode inNode){
        String currentNodeName;
        userPermissions = new List<Permission>();

        for(Dom.XmlNode childNode :inNode.getChildElements()){
            currentNodeName = childNode.getName();

            // When we come across a node that we want to handle, we simply create
            //   a new instance of the appropriate inner class, and pass the node
            //   to its process() method.
            // Each inner class should specialize in handling a specific type of XmlNode.
            // This helps to prevent the parsing logic from getting too unwieldy in 
            //   any one class.
            if(currentNodeName == 'userPermissions'){
                Permissions perm = new Permission();
                perm.process(inNode);

                // After we're done processing the current node, we can add it
                //   to the list.
                userPermissions.add(perm);
            }
        }
    }

    // When we get down to a level where a child element contains just a string,
    //   then there's little use in creating one more level of inner classes.
    // Such child elements can just be made variables of this class.
    public class Permission{
        // Again, variable names mirror those found in the XML document.
        public String name;
        public Boolean enabled;

        public Permission(){
        }

        // Again, getters are required for visualforce
        public Boolean getEnabled(){
            return enabled;
        }

        public String getName(){
            return name;
        }

        // This method should look like it has something in common with the 'process()'
        //   method of the outer class.
        // Having the same general structure makes this pattern easy to expand to
        //   be able to parse more complicated XML structures.
        public void process(Dom.XmlNode inNode){
            String currentNodeName;

            for(Dom.XmlNode childNode :inNode.getChildElements()){
                currentNodeName = childNode.getName();

                // Note: The order that you check currentNodeName values in doesn't matter at all
                if(currentNodeName == 'enabled'){
                    // For everything that isn't a string, we'll need to use the
                    //   appropriate 'valueOf()' method to convert to the correct type
                    enabled = Boolean.valueOf(childNode.getText());
                } else if (currentNodeName == 'name'){
                    // For strings, we don't need any special treatment
                    name = childNode.getText();
                }
            }
        }
    }
}

The xml that this example covers is some Salesforce metadata

<Profile xmlns="http://soap.sforce.com/2006/04/metadata">
    <custom>true</custom>
    <userLicense>Salesforce</userLicense>
    <userPermissions>
        <enabled></enabled>
        <name></name>
    </userPermissions>
    <userPermissions>
    </userPermissions>
    <!-- etc... -->

The idea is that by having each deserialization class contain a process() method, and calling that in the constructor, we can spread the load of extracting data from the XML to make each level of processing more manageable.

returning xml

A Visualforce page is actually a really easy way to accomplish this. Instead of html tags and things like <apex:pageBlockTable>, you simply make your VF page spit out the xml tags you want. <apex:repeat> is still very helpful here. The VF page is your template, and generates your xml using the data you get from your controller and/or extension.

// If there's any page parameters that need setting, now's the time to set them
Map<String, String> pageParams = ApexPages.currentPage().getParameters();

pageParams.put('id', '<some sobject record id here>');

ApexPages.PageReference myXmlTemplate = Page.XmlTemplate;

// Be careful, because this counts as a callout (no DML, scheduling a batch, enqueuing a queueable,
//   future method call, etc... before calling this)
// This won't work when called as part of a test method
// I don't think the resulting Blob is base64 encoded, and should be able to be used in
// HttpResponse.setBodyAsBlob()
Blob xmlContent = myXmlTemplate.getContent();

One last warning

While this helps make XML data more usable in Apex, it's not an insignificant amount of work.

There's also no tool I know of to do the work for you (unlike the JSON2Apex heroku app thingy that works for JSON). Every different XML payload you work with will probably need its own set of deserialization classes/logic/tests (it's very domain-specific).

Testing these things is a pain unto itself.

1
  • Thank you both of you. Looks like it's a great deal of work to work with xml. I might just switch to JSON. I am trying to figure out how would I parse the above mentioned nested payload, if it was in JSON.
    – Student
    May 8, 2019 at 14:55
0

You can use my XML Serializer. Here is the link to it: https://github.com/skolakan/Apex-XML-Serializer

Here is how you can approach it 1. Create Apex class that matches the XML Since yours is a simple XML, you can just convert it to JSON (there are many online converters for ex:https://codebeautify.org/xmltojson and use https://json2apex.herokuapp.com/ to convert json to Apex). Change the class names to be different from variable names to avoid confusion. I got the following class

public class clsContactData {

    public clsTestContact TestContact;

    public class clsTestContact {
        public clsVendorData vendorData;
        public clsContact Contact;
    }

    public class clsVendorData {
        public String CompanyAffiliation;
        public String Vendor;
        public String VendorID;
    }

    public class clsContact {
        public String FirstName;
        public String LastName;
    }
}
  1. Use XML Serializer to deserialize your XML to Apex object

enter image description here

    XMLSerializer serializer = new XMLSerializer();

    clsContactData contactData = (clsContactData) serializer.deserialize(xmlString, clsContactData.Class, new Set<String>());

    system.debug('Contact First Name: ' + contactData.TestContact.Contact.FirstName);

Debug statement resulted in |DEBUG|Contact First Name: TestFirstName

You must log in to answer this question.

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