[SalesForce] Apex REST API to return a Map of Maps

I'm developing a custom apex REST API and running into an issue annotating my getAppDefaults method with @HttpGet. When I remove the annotation and run a test class against the getAppDefaults method, I get my Map of Maps back. But I'd like this returned as a JSON structure from the GET request.

Also, if I change the method to return a String and JSON.serialize(appDefaults), I get a stringified result back from the REST requested (ie, the annotation works fine but I would like to return a JSON structure, not a string).

@RestResource(urlMapping='/v1.0/AppDefaults/*')
global with sharing class hbAppUserDefaults {

    ... //other internal methods

    @HttpGet
    global static Map<String, Map<String,Object>> getAppDefaults() {
        Map<String, Map<String,Object>> appDefaults = new Map<String, Map<String,Object>>();

        Map<String, String> u = new Map<String, String>(getUserDefaults());        
        Map<String, Object> cases = new Map<String, Object>(getCases());
        Map<String, Object> dashboards = new Map<String, Object>(getDashboards());
        Map<String, Object> robots = new Map<String, Object>(getRobots());

        appDefaults.put('User', u);
        appDefaults.put('Cases', cases);
        appDefaults.put('Dashboards', dashboards);
        appDefaults.put('Robots', robots);

        return appDefaults;
    }    

}

The compile error I get with the code above is as follows:
enter image description here

Any help is greatly appreciated!

Best Answer

A technique I have used for this sort of case is to directly set the JSON in the response rather than relying on the framework to do the serialization (which as you have noticed has some limitations):

@HttpGet 
global static void getAppDefaults() {

    RestResponse res = RestContext.response;
    if (res == null) {
        res = new RestResponse();
        RestContext.response = res;
    }
    try {
        Map<String, Map<String,Object>> appDefaults = ...
        ..
        res.responseBody = Blob.valueOf(JSON.serialize(appDefaults));
        res.statusCode = 200;
    } catch (Exception e) {
        res.responseBody = Blob.valueOf(
                String.valueOf(e) + '\n\n' + e.getStackTraceString()
                );
        res.statusCode = 500;
    }
}

This avoids the double encoding that returning String from the method produces.

A further advantage of the above pattern (at least in the context of managed packages) is that if you want to change the structure of the JSON in the future you can; relying on the method signature you cannot because global methods can't be changed once packaged.

A bit more information in An @RestResource Apex class that returns multiple JSON formats.

Related Topic