2

I'm trying to create an Invocable Method which allows an input of a JSON-formatted string from Process Builder as a variable. However, when I try to add the inputted JSON string using JSONGenerator.writeObjectField(), it escapes the double quotes with a backslash. This is causing the intended endpoint, Zapier, to incorrectly parse "Data" as one giant string instead of a nested object.

I've tried String.replace() and String.remove(), neither worked. Should I just ditch JSONGenerator altogether and manually construct the JSON?

Anonymous Apex:

ZapierWebhook_Invocable.RequestDetail rd = new ZapierWebhook_Invocable.RequestDetail();
rd.webhookURL = 'http://requestbin.fullcontact.com/[endpoint]';
rd.customJSON = '{"AssigneeId" : "B123456", "PermissionSetId" : "A789101112"}';
ZapierWebhook_Invocable.processRequests(new List<ZapierWebhook_Invocable.RequestDetail>{rd});

Relevant class method:

EDIT: To clarify, the Strings in bodyArray are JSON-formatted objects as strings (all Process Builder input values in the transaction), stored in the ZapierWebhook_Invocable.RequestDetail.customJSON invocable variable.

private static void sendRequest(String endpoint, List<String> bodyArray) {

    HttpRequest req = new HttpRequest();
    req.setEndpoint(endpoint);
    req.setMethod('POST');
    req.setHeader('Content-Type','application/json;charset=UTF-8');

    JSONGenerator gen = JSON.createGenerator(false);
    gen.writeStartObject();
    gen.writeStringField('Instance_URL',Url.getSalesforceBaseUrl().toExternalForm());
    gen.writeObjectField('Data',bodyArray);
    gen.writeEndObject();

    req.setBody(gen.getAsString());

    Http http = new Http();
    http.send(req);
}

Raw Body:

{"Instance_URL":"https://myDomain.my.salesforce.com","Data":["{\"AssigneeId\" : \"B123456\", \"PermissionSetId\" : \"A789101112\"}"]}

Zapier's debug:

enter image description here

UPDATE: The following workaround works as expected (assigning the return value as the request body). Not ideal...still would like to know how to get JSONGenerator to work.

private static String getBodyJSON(List<String> bodyArray) {

    String instanceURL = Url.getSalesforceBaseUrl().toExternalForm();
    String runningUserName = UserInfo.getName();

    List<String> jsonObjectList = new List<String>();
    String body;
    for(String jsonObj : bodyArray) {
        body = '{';
        body = body + '"Instance_URL": "' + instanceURL + '", ';
        body = body + '"Running_User": "' + runningUserName + '", ';
        body = body + '"Data": ' + jsonObj;
        body = body + '}';
        jsonObjectList.add(body);
    }

    return '[' + String.join(jsonObjectList,',') + ']';
}
2

JSONGenerator is hard to use because you have to exactly embody the JSON structure in the code e.g. include writeStartArray() calls.

For just about all cases, the JSON.serialize method is much easier to code because it works out the serialization based on the data passed to it:

String s = JSON.serialize(new Map<String, Object>{
    'Instance_URL' => Url.getSalesforceBaseUrl().toExternalForm(),
    'Running_User' => UserInfo.getName(),
    'Data' => bodyArray
});
req.setBody(s);

and as illustrated here works with collections such as maps and lists we well as on properties of classes. (I'm not sure what structure you are looking for: create nested maps and lists as required and then serialize that object graph.)

Note that building JSON by concatenating strings is risky as things like embedded double quotes are not escaped, meaning the code may work most of the time but will break in some data-dependent cases.

PS

If the List<String> bodyArray argument is in fact an array of JSON strings already, the best option is to change the calling code to pass the structured data instead. If you can't do that, then deserializing first should avoid the escaping:

    'Data' => JSON.deserializeUntyped(bodyArray)
  • Thanks. I've tried this but I'm still having a problem in that if there are nested objects (as JSON formatted strings) within the "Data" object, all the double quotes are being escaped still. See here: [{"Instance_URL": "myDomain.my.salesforce.com", "Running_User": "Automated Process", "Data": {"ReferenceCode__c":"someCode","Message__c":"My stackTrace","SObjects__c":"{\"AssigneeId\" : \"A123456\", \"PermissionSetId\" : \"B789101113\"}"}}] – Mike Jun 10 '18 at 18:26
  • I've hacked it together into a working state by doing the following: JSON.serialize(jsonObj).remove('\\').replace('"{','{').replace('}"','}'). I don't know how "bad" this is though =/ – Mike Jun 10 '18 at 18:46
  • @Mike It's not a good habit to get into... See the PS I just added. – Keith C Jun 10 '18 at 18:49
  • I wasn't happy about it haha. I see why your PS method would work in most cases, but yet another wrench here: I'm actually ultimately setting "Data" as a serialized sObject, which of course cannot have a deserialized Map<String,Object> as a field value. So, I've got it working by: // 1) deserializing the JSON string field on the sObj, 2) serializing the sObj, 3) deserializing the serialized sObj from #2 into a Map, 4) using Map.put(fieldName, deserialized value from #1), 5) serializing the resulting Map. // This has been quite the adventure, but thrilled it's now working - thanks! – Mike Jun 10 '18 at 19:26
  • I'm a little concerned about the inefficiencies of so much serializing/deserializing, but I don't see a way around it. – Mike Jun 10 '18 at 19:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.