5

I have a Lightning Component with an Opportunity attribute. The controller calls a helper method, which obtains the Opportunity from the component and attempts to pass it into Apex to be saved.

I am seeing the same "Unable to read Sobject" error message described in this post, but my component is alone in a Lightning App, rather than in Visualforce. It does have lookup fields populated, and I am setting sobjectType as that solution recommends.

I find that if Apex takes a Map then the Opportunity parameter can be passed. A simple String parameter can also be passed, but I believe the most straightforward approach for my scenario would be to pass the Opportunity record.

I'd appreciate any help or insight the community might be able to offer. Thank you! Here also is a post describing a similar issue.

The details below are updated to include the suggestions from Caspar Harmer

attribute inside component (updated)...

<aura:attribute name="oppty"
                type="Opportunity"
                default="{ 'sobjectType': 'Opportunity',
                           'Name': 'New Opportunity',
                           'StageName': 'Application Started'}"/>

(updated on 7/13 to include the following note) ...that the default value for of the attribute in the component is replaced with an Opportunity from Apex...

getOppty : function(component, selectedProgramId) {
    selectedProgramId = '001R0000012JKlG';
    var action = component.get("c.GetOpportunityFor");
    action.setParams({"selectedProgramId": selectedProgramId});
    action.setCallback(this, function(response){
        var state = response.getState();
        if (state === 'SUCCESS') {
            var oppty = response.getReturnValue();
            oppty.sobjectType = 'Opportunity';
            console.log('successful response... '+JSON.stringify(oppty));
            component.set("v.oppty", oppty);
        }
        else {
            console.log('helper.getOppty failed with a state of ' + state);
        }
    });
    $A.enqueueAction(action);
},

console output from getOppty...

successful response... {"Name":"Dev Application - Test Account - Jul-2016","StageName":"Application Started","Program_Applying_To__c":"001R0000012JKlGIAW","Contact__c":"003R00000147JmyIAE","CloseDate":"2016-07-13","OwnerId":"005360000024RsTAAU","sobjectType":"Opportunity"}

button click results in helper being called from javascript controller...

helper.saveOppty(component, function(response){
    var state = response.getState();
    if (state === 'SUCCESS') {
        console.log('saved successfully');
        component.set("v.oppty", response.getReturnValue());
        }
    }
    else if (state === "ERROR") {
        var errors = response.getError();
        if (errors) {
            if (errors[0] && errors[0].message) {
                alert("Error message: " + errors[0].message);
            }
        } 
        else {
            alert("Unknown error");
        }
    }
});

javascript helper method sets the sobjectType, but to no avail...

saveOppty : function(component, callback) {
    var oppty = component.get("v.oppty");
    oppty.sobjectType = 'Opportunity';

    console.log('saveOppty is called...' + JSON.stringify(oppty));

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

    action.setParams({"oppty": oppty});

    if (callback) {
        action.setCallback(this, callback);
    }
    $A.enqueueAction(action);
}

Apex controller method (updated)..

@AuraEnabled
public static Opportunity SaveOppty(Opportunity oppty) {
    system.debug('SaveOppty() is called... ' + oppty.Name);
    upsert oppty;
    return oppty;
}

console log output (added based on first answer from Caspar)...

saveOppty is called...{"Name":"Dev Application - Test Account - Jul-2016","StageName":"Application Started","Program_Applying_To__c":"001R0000012JKlGIAW","Contact__c":"003R00000147JmyIAE","CloseDate":"2016-07-11","OwnerId":"005360000024RsTAAU","sobjectType":"Opportunity"}

Error message: Unable to read SObject

The following updates were made after Junaid P Khader's recommendation that Sobjects should be passed from Lightning into Apex in a List.

JavaScript helper method...

saveOppty : function(component, callback) {
    var oppty = component.get("v.oppty");
    oppty.sobjectType = 'Opportunity';

    var opptyList = [ oppty ];

    console.log('saveOppty is called...' + JSON.stringify(opptyList));
    var action = component.get("c.SaveOppty");

    action.setParams({"opptyList": opptyList});

    if (callback) {
        action.setCallback(this, callback);
    }
    $A.enqueueAction(action);
}

Apex controller method...

@AuraEnabled
public static Opportunity SaveOppty(List<Opportunity> opptyList) {
    system.debug('SaveOppty() is called... ' + opptyList.size());
    upsert opptyList;
    for (Opportunity savedOppty : opptyList)
        return savedOppty;

    return null;
}

Console log output...

saveOppty is called...[{"Name":"Dev Application - Test Account - Jul-2016","StageName":"Application Started","Program_Applying_To__c":"001R0000012JKlGIAW","Contact__c":"003R00000147JmyIAE","CloseDate":"2016-07-12","OwnerId":"005360000024RsTAAU","sobjectType":"Opportunity"}]

Error message: Unable to read SObject

(Updated on 7/14 to identify the following work-around, which is not really an answer to the question, but does allow me to get past my current pain.)

I have opted to go with the JSON serialization approach that I had described in a comment on Caspar's answer a few days ago. By this approach I can pass the Opportunity back into Apex, and I do not need to include the "default" attribute on my component's Opportunity attribute.

It seems like this should be a stable approach, considering how the JSON class works in Apex, where serializing an Opportunity includes/exposes an "attributes" property of {"type":"Opportunity"}...

Opportunity oppty = new Opportunity(
    Name = 'My Opportunity',
    StageName = 'Initial Contact',
    CloseDate = Date.today()
);
system.debug(JSON.serialize(oppty));

debug output...

{"attributes":{"type":"Opportunity"},"Name":"My Opportunity","StageName":"Initial Contact","CloseDate":"2016-07-13"}

I found that I can update my Lightning Component helper's saveOppty function as follows, adding the "attributes" property to the object before calling stringify(). I tried simply adding this property and passing the Opportunity back in directly, but still saw the "unable to read" message. So I had to serialize the record first...

saveOppty : function(component, callback) {
    var oppty = component.get("v.oppty");
    console.log('saveOppty is called...' + JSON.stringify(oppty));

    oppty.attributes = {'type':'Opportunity'};
    var jsonOppty = JSON.stringify(oppty);

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

    action.setParams({"jsonOppty": jsonOppty});
    console.log('params are set...' + jsonOppty);

    if (callback) {
        action.setCallback(this, callback);
    }
    $A.enqueueAction(action);
}

My Apex method just needs to deserialize before saving...

@AuraEnabled
public static Opportunity SaveJsonOppty(String jsonOppty) {
    system.debug('SaveOppty() is called... ' + jsonOppty);
    Opportunity oppty = (Opportunity)JSON.deserialize(jsonOppty, Opportunity.class);
    upsert oppty;
    return oppty;
}
  • I found the time to test passing params to an auraenabled method and I wasn't able to break it! The only difference was that I only passed in Name, CloseDate and StageName. I'll try with some custom fields - oh are those fields viewable to all profiles? – Caspar Harmer Jul 12 '16 at 22:45
  • So after a bit more research, I am still unable to replicate your error. However, I noticed that adding a param to the opportunity attribute prior to calling the method does NOT work. You need to predefine the param in the defaults area first. While I did not get the error, my Opp failed to create. I've updated my answer to reflect this. – Caspar Harmer Jul 12 '16 at 23:05
  • A detail that I had left out in my original question is that the default Opportunity in the component is being replaced on init with a new Opportunity provided by an Apex method. I have tried taking the Opportunity that comes directly from the response.getReturnValue() method, and the helper's saveOppty() method was not able to read the sobject. I believe the issue is that the JavaScript is not able to recognize the Opportunity provided by Apex as an sobject. Looking at the stringified value it makes sense that I can pass it directly to Apex method that takes a Map<String, String>. – Brian Piggins Jul 13 '16 at 17:22
  • Yes, so your best bet is probably to copy all the fields that you need into the Opportunity that you predefined. I haven't found a better way round that. If the number of fields is low, that's ok - if not, your best bet is serialization. – Caspar Harmer Jul 13 '16 at 17:24
  • If I tried passing the component's default Opportunity into Apex, I was able to successfully create the record in Salesforce, but not when attempting to pass an Opportunity that was provide by Apex. "Unable to read SObject" whether it was a new Opportunity or an existing one, if it was provided to the component via Apex. – Brian Piggins Jul 13 '16 at 17:25
3

I have found that not having default attributes can cause problems with binding.

UPDATE - this applies to ALL attributes, including the sobjectType and CloseDate .

Add all attributes (even if empty at this stage) that you want to write to the server, because if they are not at least initialised in the defaults area, they don't get sent.

Also there are perhaps some missing fields that are needed for the Opportunity to deserialize properly (in the internal deserialization routine that is called prior to passing it to your @AuraEnabled method).

So perhaps you could define your oppty attribute like this:

<aura:attribute name="opportunity" 
                type="Opportunity" 
                default="{ 'sobjectType': 'Opportunity',
                           'Name': 'New Opportunity',
                           'StageName': 'Initial Contact',
                           'CloseDate':'2016-08-01'} />

Or at least set the name and stage prior to passing it to your method.

Of course, you may be doing this already - I can't see what your console output is.

This is just a long shot, because you seem to have done everything else right.

  • Also, I noticed that your @auraEnabled contains a lower case a. I know Apex is not case sensitive, but it's also worth changing... – Caspar Harmer Jul 11 '16 at 20:51
  • Thank you for those tips. Another Apex method provides the Opportunity record upon the init event, but it was not setting a StageName value. I added this and received the same error. Interestingly, if I build a JSON string of the Opportunity and pass this string from the Lightning Component into Apex, I am able to deserialize it, cast it as an Opportunity, and successfully save the record. That is how I discovered that I was missing the StageName. – Brian Piggins Jul 11 '16 at 21:28
2

I am doing the similar thing in my component. Instead of passing of type Opportunity I am passing JSON string to method. Convert your attribute into JSON ans string and pass it to apex method. In apex method deserialize the JSON string into Opportunity.

This is working fine for me. In the same way I am passing List of object from component to apex methods. JSON string > Apex method > deserialize.

  • Very helpful, thanks! Nothing else was working for me. – Bigears Oct 25 '16 at 12:26
1

It is suggested to pass the record as a List when Passing records to the apex controller. The format is List<Opportunity>

  • Can you share a link to where that suggestion is made? Thanks! – Brian Piggins Jul 12 '16 at 16:12
  • I dont have any link. I experienced a situation when my package was rejected by security review for not passing records as List. – Junaid P Khader Jul 13 '16 at 4:30
0

I have tried with passing object directly, it worked for me no need of stringfy e.g i have used used Attribute type is Task and assigning this value to from Main component and in child component controller class i am setting param as component.get("TaskAttribute"); and passing to server side controller static void testMethod(Task tsk)

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.