You can use the User Interface API
for this requirement.
You can run a very simple anonymous script to see what kind of data you'll get back:
Contact record = [SELECT Id FROM Contact LIMIT 1];
String endpoint = Url.getSalesforceBaseUrl().toExternalForm() +
'/services/data/v41.0/ui-api/record-ui/' + record.Id;
HttpRequest request = new HttpRequest();
request.setHeader('Authorization', 'OAuth ' + UserInfo.getSessionId());
request.setEndpoint(endpoint);
request.setMethod('GET');
HttpResponse response = new Http().send(request);
system.debug(response);
For me, that data looks something like:
{
"layoutUserStates": {
/*irrelevant properties*/
},
"layouts": {
"Contact": {
"012000000000000AAA": {
"Full": {
"View": {
/*irrelevant properties*/
"sections": [{
/*irrelevant properties*/
"layoutRows": [{
"layoutItems": [{
/*irrelevant properties*/
"layoutComponents": [{
"apiName": "OwnerId",
"componentType": "Field",
"label": "Owner ID"
}],
/*irrelevant properties*/
}, /*more items*/]
}, /*more rows*/],
/*irrelevant properties*/
}, /*more sections*/]
}
}
}
}
},
"objectInfos": {
/*so much dang metadata*/
},
"records": {
"003...": {
"apiName": "Contact",
"childRelationships": {},
"fields": {
"Account": {
"displayValue": "Edge Communications",
"value": {
"apiName": "Account",
"childRelationships": {},
"fields": {
"Id": {
"displayValue": null,
"value": "001..."
},
"Name": {
"displayValue": null,
"value": "Edge Communications"
}
},
"id": "001...",
"recordTypeInfo": null
}
},
"AccountId": {
"displayValue": null,
"value": "001..."
},
/*other fields*/
},
"id": "003...",
"recordTypeInfo": null
}
}
}
You will need to define quite a lot of structure to deserialize this data but it can be done. Here is what mine looked like after taking a few shortcuts:
class ResponseBundle { final ResponseLayouts layouts; }
class ResponseLayouts { Map<String, Map<String, Map<String, Layout>>> Contact; }
class Layout { final List<Section> sections; }
class Section { final List<LayoutRow> layoutRows; }
class LayoutRow { final List<LayoutItem> layoutItems; }
class LayoutItem { final List<LayoutComponent> layoutComponents; }
class LayoutComponent { final String apiName, componentType, label; }
And then the deserialization looks like:
Contact record = [SELECT Id FROM Contact LIMIT 1];
Id recordTypeId = SObjectType.RecordType.getKeyPrefix().rightPad(15, '0') + 'AAA';
// RecordType not enabled for Contact in my org
// otherwise you would just use record.RecordTypeId
String endpoint = Url.getSalesforceBaseUrl().toExternalForm() +
'/services/data/v41.0/ui-api/record-ui/' + record.Id;
HttpRequest request = new HttpRequest();
request.setHeader('Authorization', 'OAuth ' + UserInfo.getSessionId());
request.setEndpoint(endpoint);
request.setMethod('GET');
HttpResponse response = new Http().send(request);
ResponseBundle data = (ResponseBundle)JSON.deserialize(response.getBody(), ResponseBundle.class);
Layout fullView = data.layouts.Contact.get(recordTypeId).get('Full').get('View');
Set<String> apiNames = new Set<String>();
for (Section section : fullView.sections)
for (LayoutRow layoutRow : section.layoutRows)
for (LayoutItem layoutItem : layoutRow.layoutItems)
for (LayoutComponent layoutComponent : layoutItem.layoutComponents)
apiNames.add(layoutComponent.apiName);
Now, you may notice that there is a "data"
section of the response and wonder if that gives you back the same fields. Turns out it does, but with a few extras (all the name-pointing parent objects in addition to the lookup Id. So you can shortcut this process further:
class ResponseBundle { final Map<String, Record> records; }
class Record { final Map<String, Field> fields; }
class Field { }
Id recordId = [SELECT Id FROM Contact LIMIT 1].Id;
String endpoint = Url.getSalesforceBaseUrl().toExternalForm() +
'/services/data/v41.0/ui-api/record-ui/' + recordId;
HttpRequest request = new HttpRequest();
request.setHeader('Authorization', 'OAuth ' + UserInfo.getSessionId());
request.setEndpoint(endpoint);
request.setMethod('GET');
HttpResponse response = new Http().send(request);
ResponseBundle data = (ResponseBundle)JSON.deserialize(response.getBody(), ResponseBundle.class);
Set<String> dataFields = data.records.get(recordId).fields.keySet();