[SalesForce] REST Integration – JSON Serialization/Deserialization alternative to wrapper class

I am building a REST integration between two Salesforce Environments. One environment will be used to setup and stage Data and when ready the Integration will be able to push data from the src to the target.

It is not a lot of data at any one time, but it is a lot of related objects that would need to be Upserted to the target org in the right order.

The steps for one migration looks more or less like the following:

  • Retrieve object1 data with SOQL.
  • JSON Serialize the retrieved data using a wrapper class.
  • Send the JSON as a HTTP PUT Request to the Source Org
  • Rest Webservice in the targer org gets the JSON, Deserializes it using the same wrapper class and Upserts the data

This all works and I am able to send and work with Lists of data for each object, however to get it to work I need:
One wrapper class per object, which has all the fields that I want to migrate as Class variables, then as part of the constructor then as part of a jsonSerializeMethod for serialization and as part of a getMethod for deserialization, plus as a retrieveBySOQL method to return the lists of related data.

This would not be a problem if I only had one object (or even 3) as part of the migration, but I happen to have 20ish objects, which mean I would need one such class for each object.

It is the same fields in both Salesforce Environments.

Specifically I would like to know if I can accomplish the same without using these wrapper classes for serializing/deserializing the json. Here is an example:

public class PriceListItemClass {
public String aptsExtId;

public PriceListItemClass(String aptsExtId){
    this.aptsExtId = aptsExtId;
}

public Apttus_Config2__PriceListItem__c getRecord(){
    Apttus_Config2__PriceListItem__c priceListItem = new Apttus_Config2__PriceListItem__c();
    priceListItem.APTS_Ext_Id__c = aptsExtId;
    return priceListItem;
}

public static List<Apttus_Config2__PriceListItem__c> retrieveBySOQL(Set<Id> productExtIdSet){
    List<Apttus_Config2__PriceListItem__c> priceListItems = [
        SELECT APTS_Ext_Id__c, Apttus_Config2__PriceListId__c
        FROM Apttus_Config2__PriceListItem__c
        WHERE Apttus_Config2__ProductId__r.APTS_Ext_ID__c
        IN : productExtIdSet
        ];

    priceListItems = updateAPTSExtIds(priceListItems);

    return priceListItems;
}

public static String jsonSerialize(List<Apttus_Config2__PriceListItem__c> priceListItemList){
    List<PriceListItemClass> priceListItemClassList = new List<PriceListItemClass>();

    for(Apttus_Config2__PriceListItem__c priceListItem : priceListItemList){
        PriceListItemClass priceListItemClass = new PriceListItemClass(priceListItem.Name);
        priceListItemClassList.add(priceListItemClass);
    }

    String httpRequestBody = JSON.serialize(priceListItemClassList);
    System.debug('httpRequestBody: ' + httpRequestBody);

    return httpRequestBody;
}

I am able to send a List of Products without the wrapper class: This is the JSON: [{"attributes":{"type":"Product2","url":"/services/data/v45.0/sobjects/Product2/01t0E000004dz71QAA"},"LastModifiedDate":"2019-02-28T11:24:53.000+0000","Description":"test","APTS_Enable_Reselling__c":true,"APTS_Exclude_from_SBL_PPL__c":false,"IsActive":true,"Apttus_Config2__HasOptions__c":false,"Apttus_Config2__HasSearchAttributes__c":false,"APTS_MBL_Enabled__c":false,"Apttus_Config2__ConfigurationType__c":"Standalone","Apttus_Config2__Uom__c":"Each","APTS_Foundations__c":false,"APTS_Fixed_Price__c":false,"Name":"Oracle5 – Update11","APTS_Exclude_from_MBL__c":false,"CreatedById":"005570000082wlWAAQ","pse__IsServicesProduct__c":false,"APTS_PSA_Product__c":false,"APTS_Product_Link__c":"_HL_ENCODED_/01t0E000004dz71_HL_01t0E000004dz71_HL__blank_HL_","APTS_Solution_Owner_Prod_Sum__c":" ","APTS_Follows_IMS_Users__c":false,"APTS_Price_Point__c":1,"APTS_Safe_ID__c":"01t0E000004dz71QAA","Apttus_Config2__HasAttributes__c":false,"IsDeleted":false,"Migrate_To_Production__c":false,"APTS_Pre_CPQ__c":false,"Apttus_Config2__Version__c":1.00,"Apttus_Config2__HasDefaults__c":false,"TECH_MigratePreprod__c":false,"CanUseRevenueSchedule":false,"Apttus_Config2__Customizable__c":false,"CurrencyIsoCode":"EUR","SystemModstamp":"2019-02-28T11:24:53.000+0000","IsArchived":false,"Apttus_Config2__ExcludeFromSitemap__c":false,"Is_SBL_Product__c":false,"Migrate_To_Preprod__c":true,"APTS_Quantity_Check_Against_Asset__c":false,"CreatedDate":"2019-02-21T13:48:32.000+0000","Id":"01t0E000004dz71QAA","APTS_Enable_Base_Price_Edit__c":false,"LastModifiedById":"005570000082wlWAAQ","APTS_Ext_ID__c":"01t0E000004dz71QAA","Apttus_Config2__Icon__c":"_HL_ENCODED_/apex/Apttus_Config2__IconUploader?productId=01t0E000004dz71_HL__IM1_/resource/Apttus_Config2__Image_UploadIcon_IM2_Upload Icon_IM3__50_IM4_50_IM5__HL__top_HL_"}]

I use the APTS_Ext_Id__c as the external Id.

This is the REST Ressource:

@RestResource(urlMapping='/Product/*')

global class ProductToolsRest {

//Upsert a List of Products
@HttpPut
global static void doPut() {
    RestRequest request = RestContext.request;
    RestResponse response = RestContext.response;

    String jSONRequestBody = request.requestBody.toString().trim();
    System.debug('jSONRequestBody: ' + jSONRequestBody);

    // List<ProductClass> productClassRequests = (List<ProductClass>)JSON.deserialize(jSONRequestBody, List<ProductClass>.class);

    List<Product2> productsToUpsert = new List<Product2>();
    List<Product2> productList = JSON.deserialize(jSONRequestBody, productsToUpsert);
    // for(ProductClass productClassRequest : productClassRequests){
    //     productsToUpsert.add(productClassRequest.getRecord());
    // }

    // for(Product2 product : productList){
    //     productsToUpsert.add(product);
    // }

    if(!productsToUpsert.isEmpty()){
        System.debug('productsToUpsert: ' + productsToUpsert);
        Schema.SObjectField extID = Product2.Fields.APTS_Ext_Id__c;
        System.debug('productsToUpsert: ' + productsToUpsert);

        // Database.UpsertResult[] cr = Database.upsert(productsToUpsert, extID);
        Database.UpsertResult[] cr = Database.upsert(productsToUpsert, extID);

        upsert productsToUpsert;
    }
}

}

Thank you!

Best Answer

You might be interested in the SObject tree resource from the REST API that Salesforce provides.

SObject tree allows you to use a single request to insert a record hierarchy up to 5 levels deep. The SObject tree resource is mostly aimed at creating a hierarchy of records. If you're not looking for that per se, you may want to simply insert single records using the Composite resource. You may also try the composite resource if your record hierarchy is greater than 5 levels deep.

For the cost of understanding and prototyping a solution using this, you get to avoid the need to create any wrapper classes (if the SObjects in both orgs share your target fields).

I'm assuming that you already have the authentication part taken care of, since you already rolled your own cross-org integration using an Apex webservice.

Related Topic