0

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?

1 Answer 1

3

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})

3
  • Awesome! Looks like that goes through a different code path so it works. Basically, for a master-detail relationship, the key is that you need to add something like with totalSize, and done, otherwise this won't work. {"RelatedObjects__r": {"totalSize": 2, "done": true, "records": [{...}, {...}]}}
    – jon_wu
    Commented May 18, 2017 at 2:32
  • @jon_wu - so my answer does or does not work with MD relationships?
    – Eric
    Commented May 18, 2017 at 2:34
  • 1
    Your answer works, but my comment was just clarifying that you have to add totalSize, done, records keys under a related objects when you're making the JSON. Normally, you wouldn't be making the JSON from Salesforce, so the natural thing to do would be to have a list of related objects like this: {"RelatedObjects__r":[{...}, {...}]}. Not only do you need to have the list underneath a records key, but you also need to add in totalSize and done: true.
    – jon_wu
    Commented May 18, 2017 at 3:04

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .