Developers

What Is a Wrapper Class in Salesforce?

By Oleh Mykytyn

Salesforce is a big and powerful platform, both for users and for developers. It provides a lot of pre-built tools and out-of-the-box features, but there are often challenges when something unique is needed – when custom development and complex data type manipulation is required.

This is where a Wrapper Class comes to the rescue. But what is a “Wrapper Class”, and how is it used? This guide will provide everything you need to know.

What Is a Wrapper Class?

A Wrapper Class, in simple terms, is an object created in Apex – similar to a custom object but created in code. This data lives only in code during your transaction and does not consume database storage. It’s a data structure or an abstract data type that can contain different objects or collections of objects as its members. Its instances could be different data types:

  • Primitive
  • Collections
  • sObjects

What Are the Benefits of a Wrapper Class?

A Wrapper Class provides multiple benefits including:

  1. The possibility of converting the JSON string into an object (and vice versa) with little effort.
  2. Helping to combine different types of appropriate data and organizing it efficiently.
  3. The ability to complete a variety of calculations during data organization.

Also, a Wrapper Class can be used in Lightning Web Component (LWC), AURA, and Visualforce.

Use Cases of a Wrapper Class

Now, let’s take a look at some use cases with examples of a Wrapper Class usage in LWC.

Convert JSON Strings into an Object

One of the key features of a Wrapper Class is the possibility to convert JSON strings to object data types very easily.

In this example, we’ve received data from another system via REST API and have to show it on the Record page without saving the data to Salesforce. This can be done easily by creating a Wrapper Class with the required data structure and using JSON.deserialize to convert it.

1. Let’s imagine we have the next JSON-type response received in Apex from another system, and we’d like to show this data on the record page.

  {
   "Status":"200",
   "Message":"Order Items are successfully created in the External System with next Data.",
   "OrderNumber":"00000100",
   "OrderItems":[
      {
         "ExternalOrderId":"ORDER-IT-145237",
         "Name":"Screen size 21 inch",
         "Family":"Hardware",
         "SKU":"card-rts-21",
         "OrderDate":"1547250828000"
      },
      {
         "ExternalOrderId":"ORDER-IT-612753",
         "Name":"Full HD Screen with 51 inch",
         "Family":"Hardware",
         "SKU":"card-rtp-51",
         "OrderDate":"1547250828000"
      }
   ]
}

2. A Wrapper Class will help us replicate the same data structure in Apex. To do this, we have to create a Class with the same variables and the same structure as it is in JSON.

In this case, we will have two Wrapper Classes as our JSON contains a nested array for Order Items. We’ll have a list of a Wrapper Class OrderItem in ResponseBody Wrapper Class.

  public class ResponseBody {
       @AuraEnabled public String Status;
       @AuraEnabled public String Message;
       @AuraEnabled public String OrderNumber;
       @AuraEnabled public List<OrderItem> OrderItems;
   }
   public class OrderItem {
       @AuraEnabled public String ExternalOrderId;
       @AuraEnabled public String Name;
       @AuraEnabled public String Family;
       @AuraEnabled public String SKU;
       @AuraEnabled public String OrderDate;
 
   }

3. After that, we can use a magic command in Apex to do the work for us. Deserialize command will transform the string to the type of object with the data type we created in a Wrapper Class.

ResponseBody responseBody = (ResponseBody)System.JSON.deserialize(json, ResponseBody.class);

4. After that, we can easily send this data to JavaScript in LWC. Using the next code, we’re calling the getOrdersList method from our Apex Controller OrderController, and to receive information about the exact order, we’re passing the Record Id of our current Order.

import { LightningElement, track, wire, api } from 'lwc';
import getOrdersList from '@salesforce/apex/OrderController.getOrdersList';
export default class OrdersList extends LightningElement {
 
   @api recordId;
   @track data = {};
   @wire(getOrdersList, {orderId: '$recordId'})
  
   wiredData(result) {
       if (result.data) {
           let parsedData = JSON.parse(JSON.stringify(result));
           this.data = parsedData.data;
       }
   }
}

5. The last thing is to show data in an appropriate way in the component. We’re using each iteration to show all records in a table.

           <template if:true={data}>
               <h3>Order №:{data.OrderNumber}. {data.Message}</h3>
               <table>
                   <tr>
                       <th>External Id</th>
                       <th>Product Name</th>
                       <th>Family</th>
                       <th>SKU</th>
                       <th>Order Date</th>
                   </tr>
 
                   <template for:each={data.OrderItems} for:item="item">
                       <tr key={item.ExternalOrderId}>
                           <td>{item.ExternalOrderId}</td>
                           <td>{item.Name}</td>
                           <td>{item.Family}</td>
                           <td>{item.SKU}</td>
                           <td>
                              <lightning-formatted-date-time value={item.OrderDate}>
                              </lightning-formatted-date-time>
                           </td>
                       </tr>
                   </template>
 
               </table>
           </template>   

6. Now, we’re ready to add this LWC to the lightning record page and to see the result. Before doing this, be sure to change the isExposed attribute to “true” and add your target page to properties in Lightning Web Component XML file.

<isExposed>true</isExposed>
<targets>
   <target>lightning__RecordPage</target>
</targets>

7. Here are the next steps:

  • Navigate to the Record Detail page.
  • Click on Gear icon and select Edit Page.
  • On the left side in the Custom Components section, find your orderConsolidateData component and drag it to the page.
  • Click Save and activate.

8. As you can see, our table with data from JSON is shown on the page. Our goal has been reached with a few lines of code and some data conversions.

Combine and Organize Data

One more key use case is to consolidate a set of different fields from multiple Objects. For example, we need to show a list of rows that contain data from two different objects that are not related, and map them by some criteria.

Here would be a good place to use a Wrapper Class with a new data type that will consolidate fields from both objects and then iterate via this list.

1. First, we need to get records from both objects that we are going to consolidate. In our example, it’s Order Item and Product Warranty.

       // Querying Order Items of Order by OrderId received from UI
List<OrderItem> orderItems = [SELECT Id, OrderId, OrderItemNumber, PricebookEntry.Product2.Name, PricebookEntry.Product2Id, Product2Id, Order.BillToContactId, Quantity, UnitPrice
                                   FROM OrderItem
                                   WHERE OrderId =:  orderId];
 
List<Product_Warranty__c> productWarranties = [SELECT Id, Name, Contact__c, Product__c, Warranty_End_Date__c, Is_Active__c, Note__c
                                            FROM Product_Warranty__c];

2. The next step will be to map Product Warranty records with a unique key that will be used in the Order Item loop to find a matching Product Warranty record.

       Map<String, Product_Warranty__c> productWarrantyByKey = new Map<String, Product_Warranty__c>();
       //sorting Product Warranty records by key contained from Contact and Product
       for(Product_Warranty__c productWarranty : productWarranties){
           String productWarrantyKey = (String)productWarranty.Contact__c + (String)productWarranty.Product__c;
           productWarrantyByKey.put(productWarrantyKey, productWarranty);
       } 

3. After that, we’re ready to create a Wrapper Class with all attributes from both objects.

   // Wrapper class for combined data
   public class OrderItemProductWarrantyWrapper {
       @AuraEnabled public String OrderItemId;
       @AuraEnabled public String OrderItemName;
       @AuraEnabled public Boolean IsActive;
       @AuraEnabled public Decimal Quantity;
       @AuraEnabled public Double UnitPrice;
       @AuraEnabled public Date WarrantyEndDate;
       @AuraEnabled public String Note;
   }

4. The last step is to fill our list of Wrapper instances with combined records. In the first part, we populate variables from the record Order Item. In the second we’re finding matching Product Warranty records by Key and copying needed values to our Wrapper.

       // Creating a list of OrderItemProductWarrantyWrappers for combined records
       List<OrderItemProductWarrantyWrapper> orderItemProdWarrantyWrappers = new List<OrderItemProductWarrantyWrapper>();
 
       // Loop for populating our list
       for(OrderItem item: orderItems){
           OrderItemProductWarrantyWrapper ow = new OrderItemProductWarrantyWrapper();
 
           // Populating wrapper with item data
           ow.OrderItemId = item.Id;
           ow.OrderItemName = item.PricebookEntry.Product2.Name;
           ow.Quantity = item.Quantity;
           ow.UnitPrice = item.UnitPrice;
          
           // Populating Wrapper with consolidated data from Product Warranty by mapping with a key
           String oderItemKey = (String)item.Order.BillToContactId + (String)item.Product2Id;
           Product_Warranty__c currentProdWarranty = productWarrantyByKey.get(oderItemKey);
 
           if(currentProdWarranty != null) {
               ow.WarrantyEndDate = currentProdWarranty.Warranty_End_Date__c;
               ow.Note = currentProdWarranty.Note__c;
               ow.IsActive = currentProdWarranty.Is_Active__c;
           }
      
           orderItemProdWarrantyWrappers.add(ow);
       }

5. When data is structured, we’re passing it to our LWC and showing it in a table by iterating through the list as in the previous example. Then you should be able to see the result as this component.

Summary

Using a Wrapper Class is already a best practice, but to make it even better, here is a few final recommendation on usage.

Always try to keep your Wrapper Class as a separate Apex Class, as this will help to make it unified. It also gives another developer the chance to reuse it in future implementations, avoiding code duplication. However, if you decide that your Wrapper is very unique and there is no reason to make a separate class, in the end, it’s better to keep it as Inner Class.

The Author

Oleh Mykytyn

Oleh is a 10x Certified Technical Lead, the Architect at Redtag, and was Salesforce MVP class ‘20-21. He is a talented Salesforce specialist with an interest in executing difficult tasks

Leave a Reply