2

I'm trying to build a slack integration with an apex rest endpoint. and I need to get a message in a format such as below. Normally I would create an object and return the object, however slack uses the key "short" which is reserved in apex. I tried to generate an object, then JSON.serialize and do a replace (ex. short1 > short) and I've also tried using the JSON.generator to manually create the request but every method has escaped double quotes in the response which shows it as text instead of the proper format. I'd appreciate it if someone could help me figure out how to return a non-escaped JSON string that includes reserved (short) characters. Thanks in advance.

Response I'm getting ex. "{\n \"text\" : \"sample text\"\n}"

Final Response I'm trying to generate (note short in fields) { "attachments": [ { "color": "#01a3d6", "fields": [ { "title": "Status", "value": "Pending", "short": true } ] } ] }

my test apex class

@RestResource(Urlmapping='/v1/slack/*')
global with sharing class SlackRest{   

    @HttpGet
    global static String getRequest() {
        RestRequest req = RestContext.request;

        system.JSONGenerator gen = JSON.createGenerator(true);
        gen.writeStartObject();
            gen.writeStringField('text', 'sample text');
        gen.writeEndObject();

        String genString = gen.getAsString();

        genString = genString.replace('\\"', '"');
        genString = genString.replace('"{', '{');
        genString = genString.replace('}"', '}');

        return genString;
    }
}

1 Answer 1

0

Your JSONGenerator isn't the problem, it's the String return type. Your string is being converted from a JSON "Object" into a JSON "String", which essentially means that the system is helpfully trying to convert the response for you. If you insist on returning a string, your client will have to call JSON.parse(JSON.parse(response)) to get back to a "native" object format.

Since keywords are your problem, I suggest you simply use a Map<String, Object> and let the built-in logic convert to JSON for you.

@HttpGet global static Map<String, Object> getRequest() {
  return new Map<String, Object> {
    'test' => 'sample text'
  };
}

This behavior occurs because a client can request either JSON or XML, and the system will automatically convert your response into the appropriate type based on the client's Accept request header or type hint (e.g. using .json or .xml in the path name). By trying to explicitly return JSON, you're actually ignoring the system's default feature of handling media types.

If you really insist on using your own return values, you can also set RestContext.response.responseBody, and set the return type to void, but as a matter of practicality, I'd avoid doing that and just let the system do its job. You may later have a client that expects XML instead of JSON, and it would be quite annoying to have to rewrite all of your code later.


Personally, I'd advise that you don't use JSONGenerator. It's a lot easier to generate the correct JSON using a simple Map<String, Object>. Here's an example:

Map<String, Object> resultObj = new Map<String, Object>{
  'attachments' => new Object[] {
    new Map<String, Object> {
      'color' => '#01a36',
      'fields' => new Object[] {
        new Map<String, Object> {
          'short' => true,
          'title' => 'Status',
          'value' => 'Pending'
        }
      }
    }
  }
};

I say that it's easier in the sense that you're basically writing JSON in Object form, and then letting the system figure out the quotes and commas for you.

Note that this won't be a "perfect" version of what you originally quoted, because the keys will be in an indeterminate order, but any standards-compliant JSON parser (e.g. JavaScript's JSON built-in) will not be able to distinguish between the two.

Requested:

{ "attachments": [ { "color": "#01a3d6", "fields": [ { "title": "Status", "value": "Pending", "short": true } ] } ] }

Actual:

{"attachments":[{"fields":[{"value":"Pending","title":"Status","short":true}],"color":"#01a36"}]}
4
  • Thanks for the detailed response. I'm trying out your code but I'm getting Invalid type for Http* method: Map.Object when I try to save the basic @HttpGet return 'text' class you had above. Doing some searching I found this link: salesforce.stackexchange.com/questions/30873/… where you can create an interface but then I'm back to the reserved "short" value in the class creation if I have to define the keys. Running your later code in developer console does give me the right output but I cant save that in the httpget
    – Kevin B
    Commented Dec 4, 2016 at 16:24
  • Here was the code I was using @HttpGet global static Map<String, Object> getRequest() { Map<String, Object> resultObj = new Map<String, Object>{ 'attachments' => new Object[] { new Map<String, Object> { 'color' => '#01a36', 'fields' => new Object[] { new Map<String, Object> { 'short' => true, 'title' => 'Status', 'value' => 'Pending' } } } } }; return resultObj; }
    – Kevin B
    Commented Dec 4, 2016 at 16:39
  • @KevinB Yeah, it looks like you can't use Object after all. I'll amend this answer in a bit, but you're probably going to want to go with RestContext.response.responseBody in order to avoid having the JSON string becoming double-encoded.
    – sfdcfox
    Commented Dec 4, 2016 at 17:15
  • I figured out a solution. I'm returning back the main class then just doing a List<Map<String, String>> to get around the reserved word piece. Thanks for the suggestions sfdcfox
    – Kevin B
    Commented Dec 4, 2016 at 17:38

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .