[SalesForce] Can classes be used as types for return values for actions in Lightning Components

In a clientside controller of a Lightning Component I want to retrieve a complex structure of data. The reason is, that I don't want to fire more than on action to avoid multiple roundtrips. Therefore I want to use a class defined in APEX as return value of an action like this:

({
    init : function(component, event, helper) {
        console.log('init');
        var action = component.get("c.getData");        
        action.setCallback(this, function(response) {
            console.log(response.getReturnValue());
            // do something funny here later...
        });
        $A.enqueueAction(action);
    }, 
})

The APEX controller looks like that

public class elfClientOpportunityList { 
    public class data {
        public List<Opportunity>        opportunities               { get; set; }
        public String                   text                        { get; set; }
        public String                   text2                       = 'text2';
        public data() {
            this.opportunities = new List<Opportunity>();           
            this.text   = 'text1';
        }
    }
    @AuraEnabled public static data     getData() {
        data result = new data();
        result.opportunities = (List<Opportunity>) Database.query(
            ' SELECT Name FROM Opportunity LIMIT 3 '
        );
        return result; 
    }
    @AuraEnabled public static Opportunity[]    getOpps() {
        return (List<Opportunity>) Database.query( 
            ' SELECT Name FROM Opportunity LIMIT 3 '
        );
    }
}

Now looking a the console output I get only an empty object

enter image description here

However after I change the js-controller to fetch only a list of SObjects (using the last function of the APEX controller)

var action = component.get("c.getOpps"); 

it brings up a non-empty result (as expected)

enter image description here

Are complex data structures in form of classes supported as return values? If so, what am I doing wrong here?

Just to be sure nothing went wrong in getData() I double checked the result via execute anonymous and it works as expected:

32.0 APEX_CODE,WARN;APEX_PROFILING,WARN;CALLOUT,WARN;DB,WARN;SYSTEM,WARN
17:40:08.127 (127677683)|EXECUTION_STARTED
17:40:08.127 (127693692)|CODE_UNIT_STARTED|[EXTERNAL]|execute_anonymous_apex
17:40:08.188 (188291840)|USER_DEBUG|[235]|WARN|{
   "text2" : "text2",
   "text" : "text1",
   "opportunities" : [ {
      "attributes" : {
         "type" : "Opportunity",
         "url" : "/services/data/v35.0/sobjects/Opportunity/00626000002CW7PAAW"
      },
      "Name" : "Horst Meier - 10247 Berlin - 2015/10",
      "Id" : "00626000002CW7PAAW"
   }, {
      "attributes" : {
         "type" : "Opportunity",
         "url" : "/services/data/v35.0/sobjects/Opportunity/00626000002BVH0AAO"
      },
      "Name" : "- 12345 Berlin - 2015/10",
      "Id" : "00626000002BVH0AAO"
   }, {
      "attributes" : {
         "type" : "Opportunity",
         "url" : "/services/data/v35.0/sobjects/Opportunity/00626000002CWe2AAG"
      },
      "Name" : "Heinz Meier -    - 2015/10",
      "Id" : "00626000002CWe2AAG"
   } ]
}
17:40:08.188 (188371268)|CODE_UNIT_FINISHED|execute_anonymous_apex
17:40:08.190 (190373538)|EXECUTION_FINISHED

Best Answer

Every property returned should be annotated as @AuraEnabled.

 public class Data {

    @AuraEnabled
    public List<Opportunity> Opportunities { get; set; }

    @AuraEnabled
    public String Text { get; set; }

    @AuraEnabled
    public String Text2 = 'text2';
    public Data() {
        this.Opportunities = new List<Opportunity>();           
        this.Text = 'text1';
    }
Related Topic