Lightning Components in Visualforce – Unable to read sobject

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>    

Answer

So after quite a bit more investigation, the workaround for this is to set an sobjectType attribute on the sobject prior to sending it to the server. Thus if I update my sendToServer method as follows:

sendToServer : function(cmp, ev) {
    var action=cmp.get('c.SendAccount');
    var acc=cmp.get('v.acc');

    // set the sobjectType!
    acc.sobjectType='Account';

    action.setParams({'acc' : acc});
    var self=this;
    action.setCallback(this, function(response) {
            self.actionResponseHandler(response, cmp, self, self.sentAccount);
    });
    $A.enqueueAction(action);
}

I don’t get the error and I’m able to debug my SObject out on the server.

To re-iterate, this only seems to be an issue when the component is in a Visualforce page and has a lookup field populated. (As an aside, the same thing happens when I create the sobject client side, but I usually expect to have to specify the sobjectType there anyway).

Attribution
Source : Link , Question Author : Bob Buzzard , Answer Author : Bob Buzzard

Leave a Comment