Skip to main content

Code reviews rule: Unnecessary JSON round trip

Written by David Martin

Unnecessary JSON round trip

Why is this an issue?

Serializing an object with JSON.serialize and then deserializing the resulting string with JSON.deserialize, JSON.deserializeStrict, or JSON.deserializeUntyped without the string ever leaving the transaction is a round trip that achieves nothing the original object could not.

This can cause problems with performance because both serialization and deserialization consume CPU time, which is subject to the Apex CPU time governor limit.

You also lose type safety since the compiler can no longer check what flows between the methods.

The rule follows the value across method and file boundaries, so it also finds round trips where one class serializes and another deserializes.

Examples

Example of incorrect code, where a helper receives a JSON string instead of the object itself:

public class ContactCopier {
public static Account roundTrip(Account record) {
String payload = JSON.serialize(record);
return parse(payload);
}

private static Account parse(String payload) {
return (Account) JSON.deserialize(payload, Account.class);
}
}

Example of correct code, passing the object directly:

public class ContactCopier {
public static Account roundTrip(Account record) {
return process(record);
}

private static Account process(Account record) {
return record;
}
}

Serializing for a genuine boundary is fine. The following code is not flagged, because the deserialized value comes from the HTTP response, not from the object that was serialized:

public class CalloutSender {
public static Account send(Account record) {
HttpRequest request = new HttpRequest();
request.setEndpoint('callout:Example');
request.setBody(JSON.serialize(record));
HttpResponse response = new Http().send(request);
return (Account) JSON.deserialize(response.getBody(), Account.class);
}
}

Serializing to pass data into a @future method is also not flagged, because future methods can only take primitive parameters, so the JSON string is the platform-mandated workaround:

public class AsyncProcessor {
public static void enqueue(Account record) {
processAsync(JSON.serialize(record));
}

@future
private static void processAsync(String payload) {
Account record = (Account) JSON.deserialize(payload, Account.class);
update record;
}
}

How can I fix violations?

  1. Change the receiving method's parameter (or the producing method's return type) from String to the actual type being passed, and delete the JSON.serialize/JSON.deserialize pair.

  2. If the round trip is a deep-clone idiom such as (List<Account>) JSON.deserialize(JSON.serialize(records), List<Account>.class), use the dedicated API instead: records.deepClone() for lists of sObjects, or record.clone() for a single sObject.

Resources

Did this answer your question?