I've started seeing this error since the Spring 16 release when I retrieve an sobject from the Apex controller, update it and then send it back to the server. I originally thought this was down to a $serId attribute that was somehow added to my sobject instance in the client, but I've reproduced this without that attribute being involved.
The sequence of events is as follows:
- The component is surfaced via Lightning for Visualforce
- The component retrieves an sObject from the server
- The sobject contains a populated lookup field - this took me a while to figure out!
- The SObject is stored in an attribute
- The SObject is retrieved from the attribute
- The component invokes an action that takes the sobject as a typed parameter (i.e. not as a string)
- an error occurs of 'Unable to read sobject'
I've managed to dwindle this down to a very simple example.
Apex Controller - two methods, one to retrieve an Account, the other to accept an Account and debug it out:
public class bg_SObjectTestCtrl {
@AuraEnabled
public static Account GetAccount()
{
List<Account> accounts=[select id from Account limit 1];
Account acc=new Account(Name='Test Account',
ParentId=accounts[0].id);
return acc;
}
@AuraEnabled
public static void SendAccount(Account acc)
{
System.debug('Account = ' + acc);
}
}
Lightning Component:
<aura:component controller="bg_SObjectTestCtrl">
<aura:attribute name="acc" type="Account" />
<button onclick="{!c.getFromServer}">Get the Account</button><br/>
Account Name : {!v.acc.Name}<br/>
<button onclick="{!c.sendToServer}">Send the Account</button>
</aura:component>
Lightning Component Controller:
({
getFromServer : function(component, event, helper) {
helper.getFromServer(component, event);
},
sendToServer : function(component, event, helper) {
helper.sendToServer(component, event);
}
})
Lightning Component Helper:
({
getFromServer : function(cmp, ev) {
var action=cmp.get('c.GetAccount');
var self=this;
action.setCallback(this, function(response) {
self.actionResponseHandler(response, cmp, self, self.gotAccount);
});
$A.enqueueAction(action);
},
gotAccount : function(cmp, helper, acc) {
cmp.set('v.acc', acc);
},
sendToServer : function(cmp, ev) {
var action=cmp.get('c.SendAccount');
var acc=cmp.get('v.acc');
action.setParams({'acc' : acc});
var self=this;
action.setCallback(this, function(response) {
self.actionResponseHandler(response, cmp, self, self.sentAccount);
});
$A.enqueueAction(action);
},
sentAccount : function(cmp, helper, acc) {
alert('Sent account to server');
},
actionResponseHandler : function (response, component, helper, cb, cbData) {
try {
var state = response.getState();
console.log('State = ' + state);
if (state === "SUCCESS") {
var retVal=response.getReturnValue();
cb(component, helper, retVal, cbData);
}
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");
}
}
}
catch (e) {
alert('Exception in actionResponseHandler: ' + e);
}
}
})
For the sake of completeness, here is the Visualforce Page:
<apex:page sidebar="false" showHeader="false" standardStylesheets="false">
<apex:includeLightning />
<div id="lightning"/>
<script>
$Lightning.use("c:SObjectTestApp", function() {
$Lightning.createComponent("c:SObjectTest",
{},
"lightning",
function(cmp) {
// any additional processing goes here
});
});
</script>
</apex:page>
and the Lightning Application that ties these things together:
<aura:application access="GLOBAL" extends="ltng:outApp">
<aura:dependency resource="c:SObjectTest" />
</aura:application>