Locating Salesforce Compound and Component Fields in Apex and Python
- Author
- David Reed
- Published
- Words
- 843
One of the odder corners of the Salesforce data model is the compound fields. Coming in three main varieties (Name fields, Address fields, and Geolocation fields), these fields are accessible both under their own API names and in the forms of their component fields, which have their own API names. The compound field itself is always read-only, but the components may be writeable.
For example, on the Contact
object is a compound address field OtherAddress
. (There are a total of four standard Address fields spread across the Contact
and Account
objects, with a handful of others across Lead
, Order
, and so on). The components of OtherAddress
are
OtherStreet
OtherCity
OtherState
OtherPostalCode
OtherCountry
OtherStateCode
OtherCountryCode
OtherLatitude
OtherLongitude
OtherGeocodeAccuracy
.
Similarly, Contact
has a compound Name
field, as do Person Accounts, with components like FirstName
and LastName
.
So, if we're working in dynamic Apex or building an API client, how do we acquire and understand the relationships between these compound and component fields?
API
In the REST API, the Describe resource for the sObject returns metadata for the object's fields as well. This makes it easy to acquire all the data we need in one go.
GET /services/data/v43.0/sobjects/Contact/describe
yields, on a lightly customized Developer Edition, about 250KB of JSON. Included is a list under the key "fields"
, which contains the data we need (abbreviated here to omit irrelevant data points):
"fields":
Each field includes its API name ("name"
), its label, other metadata, and "compoundFieldName"
. The value of this last key is either null
, meaning that the field we're looking at is not a component field, or the API name of the parent compound field. There's no marker indicating that a field is compound.
This structure can be processed easily enough in Python or other API client languages to yield compound/component mappings. Given some JSON response
(parsed with json.loads()
), we can do
return
Likewise, we can get the components of any given field:
return
Both operations can be expressed in various ways, including uses of map()
and filter()
, or can be implemented at a higher level if the describe response is processed into a structure, such as a dict
keyed on field API name.
Apex
The situation in Apex is rather different because of the way Describe information is returned to us. Rather than a single, large blob of information covering an sObject and all of its fields, we get back individual describes for an sObject (Schema.DescribeSobjectResult
) and each field (Schema.DescribeFieldResult
). (We can, of course, call out to the REST Describe API in Apex, but this requires additional work and an API-enabled Session Id).
Moreover, Schema.DescribeFieldResult
does not include the critical compoundFieldName
property.
... or rather, it isn't documented to include it. In point of fact, it does contain the same data returned for a field in the API Describe call, as we can discover by inspecting the JSON result of serializing a Schema.DescribeFieldResult
record.
Unlike some JSON-enabled Apex magic, we can get to this hidden value without actually using serialization. Even though it's undocumented, these references compile and execute as expected:
Contact.OtherStreet..compoundFieldName
and
Contact.OtherStreet..
This makes it possible to construct Apex utilities like we did in Python to source compound fields and compound field components. In Apex, we'll necessarily be a bit more verbose than Python, and performance is a concern in broad-based searches. Both finding compound fields on one sObject and locating component fields for one compound field take between 0.07 and 0.1 second in unscientific testing. Your performance may vary.
Then,
System.;
yields
14:15:14:523 USER_DEBUG [1]|DEBUG|(OtherStreet, OtherCity, OtherState, OtherPostalCode, OtherCountry, OtherStateCode, OtherCountryCode, OtherLatitude, OtherLongitude, OtherGeocodeAccuracy)
and
System.;
yields
22:15:30:089 USER_DEBUG [1]|DEBUG|(Name, OtherAddress, MailingAddress)
Simple modifications could support the use of API names rather than SobjectField
tokens, building maps between compound field and components, and similar workflows.
+++
This post developed out of a Salesforce Stack Exchange answer I wrote, along with work on a soon-to-be-released data loader project.