2

My Apex controller returns a map<Account, list<String>> map type to my Lightning JS controller. But when I access it in my component's JS controller, the map turns into an object!

This unwanted conversion creates a few issues:

  1. Map methods like mapName.keys() don't work.

  2. The object isn't iterable, eg with for(var [key, value] of myMap.entries()

  3. In JS, unlike maps, objects can only have strings/symbols as keys. Which explains why my Apex map's sObject keys are now strings!

When I just retrieve a list of sObjects in my component controller, they are successfully stored as an object array, with the appropriate notation/syntax, ie:

{Id: "012345678912345", Name: "ABC Co"}

But in this former-map-turned-object, the sObject key has been stringified into something I can't use JSON.parse() on, ie:

[Account (Id:012345678912345, Name:ABC Co)]

Which is strange because according to this doc,

Return results from a server-side controller to a client-side controller using the return statement. Results data must be serializable into JSON format.

What am I missing here?

1

JSON and ES6 Maps just don’t play well together. You basically cannot natively deserialise a Apex controller returned JSON into a Map. If you want to iterate over the keys in the JS object you could just use the for(key in o) syntax

var jsObject = JSON.parse('{!acountMap}');

In the above JS snippet the variable jsObject is NOT an ES6 Map. It can only be deserialised to a plain JS object. The reverse is also NOT possible. JSON.stringify() on a ES6 Map won't do any thing. i.e. you cannot serialise an ES6 Map to a JSON because of the obvious reason that Object keys have to be strings and a Map key can be any Object.

Also, on the server side, Apex Map JSON serialisation is only supported when the Keys are primitive data types. See the last bullet point in Apex Map considerations in the docs

In summary you are doing the following 2 things Wrong

  1. On the server, You are trying to deserialise a Apex Map with a non-primitive type as Key
  2. On the browser, You are expecting to parse the JSON into an ES6 Map

Mark's Answer is a good way to work around point 1

5
  • 1
    Can you elaborate on this with some links or write up an example? "JSON and Es6 Maps just don’t play well together. You basically cannot natively deserialise a Apex controller returned JSON into a Map."
    – Mark Pond
    Oct 10 '18 at 22:39
  • Added some more info Mark. I'm new to stackexchange so apologies for the brevity
    – codemonkey
    Oct 11 '18 at 8:47
  • Thanks for the explanation! So if I understand this right: Apex maps can hold complex objects as keys, and JS maps can do the same... but the map is passed between the server/client using JSON, and whatever JSON-related methods SF uses doesn't handle the 'translation' from one map type to the other.
    – smohyee
    Oct 11 '18 at 20:00
  • Something I don't understand: the way the string is constructed on the map returned to JS from apex, which I wrote out in my post. That's not a JSON format for object notation - I thought that's what Apex would return?
    – smohyee
    Oct 11 '18 at 20:02
  • 1
    As JSON serialisation is not supported on Apex non-primitive types, the serialisation process seems to call the toString() method on the Account object and forces that as the Key. In any case, as you pointed out, that key is useless because you can’t do anything with it in JS unless you invent some convoluted mechanism to parse it :)
    – codemonkey
    Oct 11 '18 at 20:48
1

Using the sobject itself as the JSON property (the map key) is not recommended because JSON properties must be strings. Therefore your complex Account object must be turned into a string to become the key, making it very difficult to use.

One possible approach would be to use a class as wrapper for returning your data and a simple string, such as the Account record id as the key in the map. The body of this class contains the Account record as well as your list of strings for that Account.

Based on the content in your question, the solution might start like this:

public class AccountWrapper {
    @AuraEnabled
    public Account record { get; set; }
    @AuraEnabled
    public List<String> yourList { get; set; }
}

@AuraEnabled
public static Map<Id, AccountWrapper> getMyStuff() {

    List<Account> accounts = [SELECT Id, Name FROM Account LIMIT 10];
    List<String> someStrings = new List<String>{ 'abc', 'def', 'ghi' };

    Map<Id, AccountWrapper> returnData = new Map<Id, AccountWrapper>();

    for (Account a : accounts) {
        AccountWrapper awrapper = new AccountWrapper();
        awrapper.record = a;             // the account that was queried
        awrapper.yourList = someStrings; // the list of strings related to the Account

        returnData.put(a.Id, awrapper);
    }

    return returnData;
}

Producing a JSON structure that looks similar to this for you to use in your lightning component:

{
    "012345678912345": {
        "record": {
            Id: "012345678912345",
            Name: "ABC Co"
        },
        "yourList": [
            'abc', 'def', 'ghi'
        ]
    },
    "012345678912350": {
        "record": {
            Id: "012345678912350",
            Name: "XYZ Co"
        },
        "yourList": [
            'abc', 'def', 'ghi'
        ]
    },
    "012345678912355": {
        "record": {
            Id: "012345678912355",
            Name: "MNO Co"
        },
        "yourList": [
            'abc', 'def', 'ghi'
        ]
    }
}
1
  • Thanks for the suggestion Mark, this is essentially how I ended up solving the issue. It's interesting that this process can handle parsing sObjects when they are in the Value side of the Map but not the Key, even though both Apex and Javascript support complex objects for map keys. Feels like SF left a gap by not writing a proper JSON parser to handle object keys.
    – smohyee
    Oct 15 '18 at 16:09

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.