4

I have a custom object, Shift, that has a related list of Shift Breaks. I can query this data like:

List<Shift__c> shifts = [SELECT Id, Name, (SELECT Id, Name FROM Shift_Breaks__r) FROM Shift__c];

If I then serialize this to JSON for return from a REST API (explicitly setting the REST Response body) using:

JSON.serialize(shifts, false); // Suppress null fields

I get output like:

[
  {
    "attributes": {
      "type": "Shift__c",
      "url": "/services/data/v52.0/sobjects/Shift__c/a1g1F0000019zwPQAQ"
    },
    "Id": "a1g1F0000019zwPQAQ",
    "Name": "SHFT-000417",
    "Shift_Breaks__r": {
      "totalSize": 1,
      "done": true,
      "records": [
        {
          "attributes": {
            "type": "Shift_Break__c",
            "url": "/services/data/v52.0/sobjects/Shift_Break__c/a1c1F000001DAlhQAG"
          },
          "Shift__c": "a1g1F0000019zwPQAQ",
          "Id": "a1c1F000001DAlhQAG",
          "Name": "SB-0"
        }
      ]
    }
  }
]

Ignoring the "attributes" which also do not get sent as part of the Aura Enabled response in LWC, specifically note how the related list is returned as an object with a nested array of the records in the list:

"Shift_Breaks__r": {
  "totalSize": 1,
  "done": true,
  "records": [
    ...
  ]
}

On the other hand, if the same SOQL query result is returned through an @AuraEnabled method via a wire or imperative call from an LWC, I see JSON of the form:

[
  {
    "Id": "a1g1F0000019zwPQAQ",
    "Name": "SHFT-000417",
    "Shift_Breaks__r": [
      {
        "Shift__c": "a1g1F0000019zwPQAQ",
        "Id": "a1c1F000001DAlhQAG",
        "Name": "SB-0"
      }
    ]
  }
]

Notice how in this case the related list is actually an array of the related records directly.

Why do these two APIs, which you would expect to both simply use JSON.serialize for the returned value, actually return the data differently? What's the benefit of the "totalSize" and "done" properties?

4
  • It's not really related to REST API at all. Your first example is the correct structure and what we would expect from any Apex.
    – Adrian Larson
    Sep 16 '21 at 14:46
  • @AdrianLarson OK, though there's a distinct difference between what I get over REST and AuraEnabled communication, which is why I framed it that way.
    – Phil W
    Sep 16 '21 at 15:10
  • Yeah the question is really just "Why is @AuraEnabled serialization different?"
    – Adrian Larson
    Sep 16 '21 at 16:35
  • True enough. I still don't understand the value of the other two properties in the related list object...
    – Phil W
    Sep 16 '21 at 20:59
1

I think the only logical answer is that @AuraEnabled methods are not simply using JSON.serialize to return the data or else it would be the same.

You can see the following when you test with an AuraEnabled method

@AuraEnabled(cacheable=true)
public static List<sObject> getAccount() {
    List<Account> accs = [SELECT Id, (SELECT Id FROM Contacts) from Account];
    //log shows attributes as well as totalSize and done like REST API
    System.debug(JSON.serialize(accs));
    //return ends up being different
    return [select Id, (SELECT Id FROM Contacts) from Account];
}

Even if you inspect the network tab, you'll see it's returned in the following format

"returnValue":
[
    {
        "Id":"0014P000027tizaQAA",
        "Contacts":
        [
            {
                "AccountId":"0014P000027tizaQAA",
                 "Id":"0034P00002UVEuUQAX"
            },
            ...
         ]
}
...

Clearly, Aura is doing something "extra" to remove what you identified specifically (totalSize, done) as well as the attributes property.

There's examples within documentation about the "aura serialization framework" which is probably the crux of the difference I'm hinting at. Returning Data from an Apex Server-Side Controller talks about the specific behavior when returning wrappers/instances of an apex class

When an instance of an Apex class is returned from a server-side action, the instance is serialized to JSON by the framework. Only the values of public instance properties and methods annotated with @AuraEnabled are serialized and returned.

Likewise, in Summer '21 - there was a fix for filtering internal sObject fields during serialization for Lightning Components & Visualforce

When an sObject is serialized or deserialized by the Aura serialization framework when the sObject is returned by an AuraEnabled Apex method, these fields could be leaked to the client prior to Summer '21. In Summer '21, the internal fields are filtered out by the serializer or deserializer

Going off the above, I suspect your answer as to why is @AuraEnabled serialization different is that it has its own serialization framework that filters out those "other" attributes you identified.


Why might it do that?

  • totalSize contains the total number of records that match a query
  • done means there are no more results to obtain

Both are essentially related to the chunking functionality of the REST API query endpoint. If more than 2,000 results are returned, totalSize and done would be essential to understand there's more left to go through and to "get" the rest if you're interested.

In the apex context, we don't have that functionality so it's possible it's removed for that reason similar to how attributes would provide no value (url)


Another fun little difference

Even REST and SOAP seem to use diffent properties to convey a similar message in their respective query() endpoints. SOAP API uses size, while REST API uses totalSize.

1
  • 1
    A lovely bit of research and explanation. You even made sure to answer my question about what totalSize and done are (though, TBH, if they are only required by the REST API query endpoint, why pollute all the other API's representations with this? Feels like the developer for REST API query endpoint shrugged and said, "I'll make sure JSON.serialize does what I need"). Anyway, that's likely ancient history and we're stuck with it and the inconsistent structuring of the JSON response.
    – Phil W
    Sep 24 '21 at 15:17

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.