14

Seems Spring 19 wants stricter typing for aura attributes, but I'm not finding any best practices on how to approach this.

This use to work in Winter 19:

MainController.js

let myData = {
  parentId: "123abc",
  sourceData: [
    {
      childId: "1",
      Some_Bool_Field__c: true
    },
    {
      childId: "2",
      Some_Bool_Field__c: true
    }
  ]
}
helper.engineService(component).sendMyData(
  myData
)

EngineService.cmp

<aura:method name="sendMyData" action="{! c.handleSendMyData }">
  <aura:attribute name="data" type="Object"/>
</aura:method>

EngineServiceController.js

handleSendMyData : function(component, event, helper) {
  var params = event.getParam("arguments");
  var action = component.get("c.goGetMyData");
  action.setStorable();
  action.setParams({
    data : params.data
  });
  helper.dispatchAction(component, action, params); //enqueues action
},

Server

@AuraEnabled
public static Map<String, Object> goGetMyData(Map<String, Object> data) {
  // In Spring 19, we don't even see this next line
  System.debug('appendEngineCache data: '+data); 

  // winter 19 use to parse these correctly
  Id recordId = (Id)data.get('parentId');
  List<Children__c> someChildren = (List<Children__c>)data.get('sourceData');

  // more
}

In Spring 19, I'm not seeing any serverside debugs other than enter and immediately exit Aura with this client-side error:

"Value provided is invalid for action parameter 'data' of type 'Map'"

Here's what I've tried so far with no success:

  • change aura attribute type Object to Map (with and w/o default="{}").
  • change serverside argument from Map<String, Object> to Object and it gacks

Here's what works, but not ideal (so I'm looking for alternatives)

  • Use JSON.stringify(data) and changing apex argument to String then use Map<String, Object> parsedData = (Map<String, Object>)JSON.deserializedUntyped(data) inside the method.
  • Then, continue to use parsedData in any downstream Apex.

Anyone found any alternatives without that additional type coercion overhead?

EDIT: Possible solution until more info?

Given that existing unit / integration tests sometimes need to call these methods (with Map<String, Object> parameters), this is the least path of resistance I've found is to overload existing method AND use JSON.stringify at some point in clientside.

Client

action.setParams({
  data : JSON.stringify(params.data)
});

Server

@AuraEnabled
public static Map<String, Object> goGetMyData(String data) {
  return goGetMyData((Map<String, Object>)JSON.deserializeUntyped(data));
}

@TestVisible
private static Map<String, Object> goGetMyData(Map<String, Object> data) {
  // all existing code
  // but @TestVisible added and switched over to private so aura component doesn't find the wrong one.
}
2

2 Answers 2

8

Fix for this issue(W-5724071) is in the upcoming patch (scheduled to be released in mid Jan).

In Spring'19 salesforce is addressing various long standing issues with deserialization pertaining to apex actions. Brief list of enhancements include,

  • Make conversion of all primitive apex data types work for positive use cases. There were a few reported issues here by the community (e.g. link).
  • Make incorrect usage error out with a clear develop error. Currently any incorrect usage results in internal server error (resulting in noise for us and useless stack-id for developer)
  • Add support POJO style user defined Apex types
  • Make handing of SObjects consistent
  • Add support for nested lists/maps

I have also reached out to documentation team to ensure more details of the enhancements get incorporated in the public docs.

2
  • 1
    An example of each of the use case will be handy .Also community can help you testing few of these . Commented Jan 8, 2019 at 3:30
  • 1
    Wonderful, would definitely be interested in how POJO is represented in Apex in the public docs as this was my intent behind leaving JSON off the table.
    – tsalb
    Commented Jan 8, 2019 at 3:48
1

If you don't want to use JSON serialization and deserialization downtrip, you can use Inner classes. Just make sure you have setter and getter in your Aura Enabled Attribute

So your Apex code will be:

public class MyApexClassController {
    @AuraEnabled
    public static ApexWrapper goGetMyData(ApexWrapper data) {
        System.debug('appendEngineCache data: ' + JSON.serialize(data));   
        ApexWrapper ad= new ApexWrapper();  
        ad.parentId ='222';
        return ad;



    }   

    public class ApexWrapper{
        @AuraEnabled
        public String parentId {get;set;}   //123abc       
        @AuraEnabled
        public List<Map<String,Object>> sourceData{get;set;}        

    }

}

JS Code:

({
    onButtonClick : function(component, event, helper) {


        let myData = {
            parentId: "123abc",
            sourceData: [
                {
                    childId: "1",
                    Some_Bool_Field__c: true
                },
                {
                    childId: "2",
                    Some_Bool_Field__c: true
                }
            ]
        }
        let action = component.get("c.goGetMyData");
        action.setParams({
            data : myData
        });
        action.setCallback(this, function(response) {
            var state = response.getState();
            if (state === "SUCCESS") {
                // Alert the user with the value returned 
                // from the server
                alert("From server: " + response.getReturnValue());
                console.log(JSON.stringify(response.getReturnValue()));
                component.set("v.myData",response.getReturnValue());
                // You would typically fire a event here to trigger 
                // client-side notification that the server-side 
                // action is complete
            }
            else if (state === "INCOMPLETE") {
                // do something
            }
                else if (state === "ERROR") {
                    var errors = response.getError();
                    if (errors) {
                        if (errors[0] && errors[0].message) {
                            console.log("Error message: " + 
                                        errors[0].message);
                        }
                    } else {
                        console.log("Unknown error");
                    }
                }
        });
        $A.enqueueAction(action);
    }
})

This automatically serializes into proper Apex type and System.debug gets printed

2

You must log in to answer this question.

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