[SalesForce] sObject array parameter in Lightning causes Internal Salesforce.com Error in Apex controller

Why do I get an Internal Salesforce.com Error when trying to pass an array of sObject values to an Apex controller action method?

I've posted a simple gist to demonstrate this problem to GitHub: https://gist.github.com/martyychang/76867c92f19808b6a7b7

The way to reproduce the problem is:

  1. Enable debug logging for yourself
  2. Launch oneLead.app (after you've created everything in the gist and tweaked the namespace references)
  3. Submitting a single lead works fine using the first Submit button, as expected
  4. Adding multiple leads and then submitting all of them using the bottom Submit button causes the Internal Salesforce.com Error

Is there something simple that I'm doing wrong? All I want to do is pass a List object as a parameter to my Apex controller from my Lightning component.

Best Answer

The problem is deep inside of the transport/marshalling interface layer between Lightning Components and Apex and will need to be fixed by my team.

For now I have developed a generic workaround using a small amount of Apex.

public class OneLeadController {

    @AuraEnabled
    public static Id createLead(Lead newLead) {
        insert newLead;
        return newLead.Id;
    }

    @AuraEnabled
    public static List<Id> createLeads(String newLeads) {
        List<SObject> newSObjectsList = convertJSONToListOfSObject(newLeads);

        insert newSObjectsList;

        List<Id> newIds = new List<Id>();
        for (SObject o : newSObjectsList) {
            newIds.add(o.Id);
        }

        return newIds;
    }

    @AuraEnabled
    public static Lead newLead() {
        return (Lead)Lead.sObjectType.newSObject(null, true);
    }

    private static List<SObject> convertJSONToListOfSObject(String json) {
        Object[] values = (Object[])System.JSON.deserializeUntyped(json);

        List<SObject> newSObjectsList = new List<SObject>();
        for (Object v : values) {
            Map<String, Object> m = (Map<String, Object>)v;

            Schema.SObjectType targetType = Schema.getGlobalDescribe().get((String)m.get('sobjectType'));

            SObject o = targetType.newSObject();

            Map<String, Schema.SObjectField> fields = targetType.getDescribe().fields.getMap();
            for (String fieldName : m.keySet()) {
                // Filter out any psuedo fields such as LastNameLocal
                Schema.SObjectField fi = fields.get(fieldName);
                if (fi != null) {
                    if (fi.getDescribe().isCreateable() && fi.getDescribe().isUpdateable()) {
                        o.put(fieldName, m.get(fieldName)); 
                    }
                }
            }

            newSObjectsList.add(o);
        }

        return newSObjectsList;
    }
}

and a small corresponding change to the client side controller (oneLeadController.js) calling this:

({
    save : function(component, event, helper) {
        var self = this;

        var createLeads = component.get("c.createLeads");
        createLeads.setParams({
            "newLeads": $A.util.json.encode(component.get("v.leads"))
        });

        createLeads.setCallback(self, function(a) {
            if (a.getState() === "SUCCESS") {
                console.log("returned: %o", a.getReturnValue());
            } else {
                alert($A.util.json.encode(a.getError()));
            }
        })

        $A.enqueueAction(createLeads);
    }
})
Related Topic