JSON Deserialization Techniques in Salesforce
If you have ever been given a JSON object and needed to parse it in Apex, then you may have run into some of the same issues we did, so read on. JSON, which stands for JavaScript Object Notation, is widely used due to its ability to define the structure of an object and its values at the same time. For Apex purposes, it is used for many different purposes such as Web Service requests and responses as well as exchanging data with a Visualforce page so its pretty important to know how to handle it properly. While there is built-in Apex support, the documentation unfortunately leaves a little to be desired, leaving you in the dark. Read on if you want to learn more. You can also read more about the JSON specification.
1. Understanding JSON Structure
2. Untyped Parsing of JSON Structure
3. Parsing JSON Structures in a Type-Safe Manner
1. Understanding JSON Structure
First it is important to understand at a very high level the structure of JSON.
{} delimits the JSON object. Additionally, they are used within a JSON object to indicate key-value pairs.
[] denotes an array (or list) of records, each of which can be either a single value such as a Name or Sales Price, another array or a list of unlimited name-value pairs (such as properties about a Person – Name, Address, Phone, Email).
Let’s use the following JSON object as an example throughout this article – this is a response from a web service that handles requests for Customer details. The JSON object contains 2 elements, one is an array of Customers and the other is a simple number, Count, containing the value of records processed successfully. For each Customer in the request, there is an array containing that Customer’s properties.
In this example, there are three records in our JSON object, two of which were a success and one that contains an error.
To help identify the elements of a JSON object, blue boxes surround name-value pairs (properties) and red boxes surround the JSON object and its child objects.
Take note of the syntax.
- The entire JSON object is surrounded by curly braces {}.
- The JSON object has two properties: Customers and Count.
- The value of the Customers property is an array, which is denoted by square brackets [].
- The value of the Count property is a number.
- The array contains three child JSON objects.
- The first and second child JSON objects have a similar structure, each containing properties that have string values.
- Each property is represented as a name:value pair.
- Quotes around the property name are optional unless the name contains spaces in which case they are required.
- Quotes around the property value are required if it is a string value. For instance, Count’s value is not in quotes as it is an Integer value.
- Properties are separated by commas. There is no comma after the last property in a JSON object.
So it is a Map (key-value pairs) with two keys:
- Customers
- Count
For the Customers key, there is an array of key-value pairs, one for each Customer requested, namely: John Smith, Jane Doe, and Michael Johnson.
So for above example, we have:
Key = Customers
Value = Array of 3 elements:
- {“Status”:”Success”,”FirstName”:”John”,”LastName”:”Smith”,”Email”:”jsmith@somewhere.com”,”Phone”:”703.555.1212″}
- {“Status”:”Success”, “FirstName”:”Jane”,”LastName”:”Doe”,”Email”:”jdoe@somewhere.com”,”Phone”:”540.555.1212″}
- {“Status”:”Error”,”Message”:”No Michael Johnson found.”}
For the Count key, there is a single value, the number of Customer records processed
Key = Count
Value = 2 // number of successful Customers found
2. Untyped Parsing of JSON Objects
Now that we understand the JSON structure, lets look at the untyped parsing provided by Salesforce. The top-level object corresponds to a Map<String, Object>
in Apex. Any data contained within a [] maps to a List
or Array
of Objects
.
For our example, our top-level object is a Map with two keys, the key is a String
but the values are of different types (Array
and Integer
), so Object
is used to achieve this. First, we represent the JSON object as a String for testing purposes.
String jsonResponse = '{"Customers":[' + '{' + '"Status":"Success",' + '"FirstName":"John",' + '"LastName":"Smith",' + '"Email":"jsmith@somewhere.com",' + '"Phone":"703.555.1212"' + '},' + '{' + '"Status":"Success",' + '"FirstName":"Jane",' + '"LastName":"Doe",' + '"Email":"jdoe@somewhere.com",' + '"Phone":"540.555.1212"' + '},' + '{' + '"Status":"Error",' + '"Message":"No Michael Johnson found."' + '}' + '],' + '"Count":2' + '}';
Map<String, Object> results = (Map<String, Object>)JSON.deserializeUntyped(jsonResponse);
Then the first key in our map is for Customers
. Salesforce uses an Array
or List
of Objects
to represent an array. So either one of these statements would work
List<Object> lstCustomers = (List<Object>)results.get('Customers'); Object[] aryCustomers = (Object[]) results.get('Customers');
Now for each array/list, there is a group of name-value pairs, which as we saw above, is represented by a Map
in Salesforce as follows. This is a little awkward as our List
is actually a List
of Map
objects.
for (Object customer : lstCustomers) { // now get attributes for this customer. Map<String, Object> customerAttributes = (Map<String, Object>)customer; // now loop through our customer attributes. for (String attributeName : customerAttributes.keyset()) { // handle response System.debug('========> ' + attributeName + ' = ' + customerAttributes.get(attributeName)); } }
And finally we get our count of successfully processed records.
Integer count = (Integer)(unTypedResults.get('Count')); System.debug('==========> There were ' + count + ' Customers processed successfully.');
This approach is error-prone and frankly just plain ugly. Thankfully, there is a better way to handle this map that is type-safe and much more readable.
Elevate your Salesforce chops with Salesforce Training
3. Parsing JSON Objects in a Type-Safe Manner
Now that we have seen how to do it the hard way, lets do it the easy way! Once you know the structure of your JSON object, you can build Apex classes to hold the data in a object-oriented structural fashion and its quite simple. So for our JSON object example, we start with our top-level class: Customers
.
A few tips for creating your classes.
- Data member names must exactly match (case-insensitive) the name of the attribute or element in the JSON object.
- All data members that are being mapped must have public access.
- All data members must be typed correctly to match the element type in the JSON object, otherwise a TypeException is thrown. So in our example, if Count is defined to be a String, an Exception is thrown as it should be defined as an Integer.
- NOTE: Interestingly enough, you do not have to define a default no-arg constructor. In fact, if you define a private no-arg Constructor , it still parses your object successfully.
- If you do not define a data member for one of the JSON object properties, then it will not be mapped. It will be silently ignored.
As we saw in Understanding JSON Structure, our top-level object has two keys, one for Customers
(a List
of Customer
properties) and one for Count
(a single Integer
property). So we create a class with two variables named and typed appropriately.
public with sharing class CustomersResponse {
public List<Customer> Customers;
public Integer Count;
public CustomersResponse() {
}
For each entry in the Customers List
, we have a set of properties that may or may not be defined. In the success case, all the Customer
details will be populated except Message
. In the case of an error, only Status
and Message
are populated. So we add all possible fields to our Customer
object as below.
public with sharing class Customer {
public String Status;
public String Message;
public String FirstName;
public String LastName;
public String Email;
public String Phone;
public Customer() {
}
}
Now that we have successfully defined our classes to represent our JSON structure, lets do the mapping. There are two ways to achieve this mapping with the standard Salesforce API. Both approaches are equally acceptable although the first approach is slightly cleaner as it involves one less API call.
Parsing JSON Objects: Approach 1
Type resultType = Type.forName('CustomersResponse');
CustomersResponse deserializeResults =
(CustomersResponse)JSON.deserialize(response, resultType);
System.debug('==========> deserialize() results = ' + deserializeResults);
NOTE: With the above code, in theory, it is possible to use JSON.deserialize(response,CustomersResponse.class)
, however this does not always work. Sometimes Salesforce is unable to determine the type correctly and you receive a “variable does not exist : CustomerDetails.type” compile error message). So instead, you must first use a Type.forName()
call to take care of this problem. Alternatively, you could compile all classes in your Salesforce Org together or define your class as an inner class of the class doing the deserialization work.
Parsing JSON Objects:Approach 2
Type resultType = Type.forName('CustomersResponse');
CustomersResponse readValueAsResults = (CustomersResponse)JSON.createParser(response).readValueAs(resultType);
System.debug('==========> createParser().readValueAs() results = ' +
readValueAsResults);
Tip: Testing JSON Objects
The best way to discover how to map your JSON object into a set of Apex classes is to take the web service (or mechanism that generates the JSON object) call entirely out of the equation. Create a String
with your JSON object and then call JSON.deserializeUntyped()
and see what gets return through the use of System.debug
. This is easily done in a Unit Test or in an Execute Anonymous script.
Then once you have all the plumbing in place and want to perform end-to-end testing (crucial as web service calls are not invoked during unit tests), use your dear friend, the Execute Anonymous script, to call the method directly that invokes the web service. If your invocation of the web service is in a future method, place the logic inside a separate method with public access that is called directly from the Execute Anonymous script and have your future method call it directly.
Conclusion
At first glance, it may seem as if mapping a JSON object to a set of Apex classes is a lot of work and not worth it so you just use the untyped parser (I know that is what I thought when I first came across this integration). However, as I have hopefully demonstrated, that is not the case. Understanding the JSON structure and the nuances of the Salesforce JSON APIs, it is surprisingly straightforward and simple to map a JSON object to a set of Apex classes that you can navigate just like any other Apex class.
If you’re still unsure if you’re able to handle JSON Deserialization techniques on your own, lay your fears to rest! Our team of Salesforce developers are fluent in the best practices and can offer you consultation. There are plenty of articles and videos covering common issues within the Salesforce ecosystem on our learning center. If your team is struggling with a mission-critical project and in need of additional development expertise, take a look at our services related to Salesforce technical architecture.