[SalesForce] For an SObject parameter of an Apex REST API, how can you pass in nested related objects

I have an Apex REST endpoint that takes an SObject parameter but I can't figure out how I can pass in nested objects. Is this possible out of the box?

Let's say my endpoint's code looked like this:

@HttpPatch global static void upsertAccountAndInsertOrders(Account account) {
    // The contents of this method are just for illustration.
    // The code in here is irrelevant and is never run because
    // the Apex REST endpoint magic tries to parse the incoming
    // request into an Account object but throws an error when 
    // encountering the nested data.
    for (Order__c order : account.Orders__r) {
        // There would be a list or Order__c objects in the JSON
    }
    insert account.Orders__r;
    upsert account;
}

I'd want to pass in JSON like this:

{"account": {"ExternalId__c": 123, "Orders__r": [{"Name": "Order to insert"}]}}

However, I get this response from the API (column number removed as it doesn't align with my simplified example):

HTTP 400 Bad Request
[{"message":"Expected JSON object to deserialize apex parameter from at [line:1, column:###]","errorCode":"JSON_PARSER_ERROR"}]

While this is just a simple example, I have deeply nested trees of objects with multiple master-detail relationships that I'd like to pass in to my Apex REST API, so it'd be ideal if I didn't have to write any additional boilerplate to make this work.

In looking at the output of JSON.serialize for a similar object, I saw that there was a records key, so I've tried this as well:

{"account": {"ExternalId__c": 123, "Orders__r": {"records": [{"Name": "Order to insert"}]}}}

A request like this won't return an HTTP 400, but account.Orders__r.isEmpty() (is true) and there are no records once the Apex runs.

Is it impossible to write deserialize JSON into related fields?

Best Answer

This seems to work both ways using the body of the RestRquest.

Rest Resource Class

@RestResource(urlMapping='/restsObject')
global class exampleRestClass {

    @HttpPost
    global static sObject doPost() {
        Account a = (Account)json.deserialize(RestContext.request.requestBody.toString(),Account.class);
        System.debug(a);
        system.debug(a.contacts);

        return a;
    }
}

Exec Anon Code

//Make sure we get an account with contacts
Contact tmp = [Select Id, AccountId From Contact Where AccountId != null limit 1];
Account a = [Select Id, Name, (Select FirstName, LastName From Contacts) From Account Where Id = :tmp.AccountId];


HttpRequest req = New HttpRequest();
req.setEndpoint('https://{instanceurl}/services/apexrest/restsObject');
req.setMethod('POST');
req.setHeader('Authorization','Bearer ' + userInfo.getSessionId());
req.setBody(json.serialize(a));
req.setHeader('Content-Type','application/json');

httpresponse resp = New HTTP().send(req);

Account acc = (Account)json.deserialize(resp.getBody(),Account.class);
system.debug(acc);
System.debug(acc.Contacts);

Rest class Debug

21:47:30.0 (5124568)|USER_DEBUG|[13]|DEBUG|Account:{Id=0013600000YkWKYAA3, Name=mytest}

21:47:30.0 (5393203)|USER_DEBUG|[14]|DEBUG|(Contact:{AccountId=0013600000YkWKYAA3, FirstName=Test, Id=0033600000prcBKAAY, LastName=Contact}, Contact:{AccountId=0013600000YkWKYAA3, FirstName=First, Id=0033600000vYfTeAAK, LastName=Last})

Exec Anon Debug

21:53:35.31 (268866459)|USER_DEBUG|[16]|DEBUG|Account:{Id=0013600000YkWKYAA3, Name=mytest}

21:53:35.31 (269053369)|USER_DEBUG|[17]|DEBUG|(Contact:{AccountId=0013600000YkWKYAA3, FirstName=Test, Id=0033600000prcBKAAY, LastName=Contact}, Contact:{AccountId=0013600000YkWKYAA3, FirstName=First, Id=0033600000vYfTeAAK, LastName=Last})

Related Topic