The Salesforce Summer ’16 release will be coming to an instance near you very soon. This post contains a look at a useful new method on the sObject class, getPopulatedFieldsAsMap(). Come to the next meetup of the Lehigh Valley Salesforce Developer Group in person or watch the live stream (recorded for later, too) to join an in-depth presentation and discussion of even more Summer ’16 features.
A Map of Populated sObject Fields
How often have you gotten the error “sObject row was retrieved via SOQL without querying the requested field”? It can happen a lot during development, or in scenarios where records or fields are being used dynamically in a different way than how they were queried. With the new sObject.getPopulatedFieldsAsMap() you can get a map that guarantees that you won’t get that error.
Aside from being able to avoid the dreaded “sObject row…” error, there are a few interesting things about the method. If one of the fields is an sObject, itself, you can cast it and call getPopulatedFieldsMap() on it (e.g., Contact.Account, Case.Account, etc.). Another interesting thing is that if you manually set a field on a record, even without querying that field, it will also show up in the map. This allows you to alter the record in memory and get the map with the included changes. Additionally, if the record does not have a value set for a field that is queried, that will not be part of the returned map. The Id of the record is always returned and if there is a record type it is always returned as well, even if it isn’t in the SOQL query.
There are some code snippets that use the getPopulatedFieldsAsMap in the Apex Developer’s Guide documentation of the method. I’ve expanded upon them a bit with some of my own tinkering below.
A contrived example that writes out fields of queried Cases:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public void debugFields(List<String> fields, String objName) { String soql = ' Select ' + String.join(fields, ',') + ' From ' + objName; for (sObject obj : Database.query(soql)) { Map<String, Object> fieldValues = obj.getPopulatedFieldsAsMap(); for (String fieldName : fieldValues.keySet()) { System.debug(fieldName + '=' + fieldValues.get(fieldName)); } } } List<String> fields = new List<String>{'CaseNumber','Status'}; debugFields(fields, 'Case'); |
Here’s a slightly more complex example that generically writes out parents and children of an sObject. The test code uses an Account’s Cases and the Name of each Case’s Contact. Note the fun use of recursion in two places!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
public void debugFields(List<String> fields, String objName) { String soql = ' Select ' + String.join(fields, ',') + ' From ' + objName; List<sObject> objs = Database.query(soql); debugFields(objs); } public void debugFields(List<sObject> objs) { for (sObject obj : objs) { debugFields(obj.getPopulatedFieldsAsMap()); } } public void debugFields(Map<String, Object> fieldValues) { for (String fieldName : fieldValues.keySet()) { if (fieldValues.get(fieldName) instanceof List<sObject>) { System.debug('------Begin Child------'); debugFields((List<sObject>) fieldValues.get(fieldName)); System.debug('------End Child------'); } else if (fieldValues.get(fieldName) instanceof sObject) { System.debug('------Begin Parent------'); sObject obj = (sObject) fieldValues.get(fieldName); debugFields(obj.getPopulatedFieldsAsMap()); System.debug('------End Parent------'); } else { System.debug(fieldName + '=' + fieldValues.get(fieldName)); } } } List<String> fields = new List<String>{'AccountNumber, (Select CaseNumber, Status, Contact.Name From Cases)'}; debugFields(fields, 'Account'); |
The output of calling that with one Account that has one Case with its Contact set is the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
AccountNumber=56789 Id=0012300000ACIExABO RecordTypeId=012A0000000ZZIAI ------Begin Child------ Id=50023000000xkLLABB CaseNumber=00012345 Status=New ContactId=00323000003Z00hkXz RecordTypeId=012F0000001Zull8z ------Begin Parent------ Id=00323000004h0lkxzZ Name=Jill Contact ------End Parent------ ------End Child------ |
More Learning
It was fun to play around with a useful new method, but it is interesting and worthwhile to explore all aspects of the release. Come to the next meetup of the Lehigh Valley Salesforce Developer Group or watch it to join an interesting discussion and learn more!
The methods to generically traverse SOQL results will be really useful. Thanks.
For me the biggest win with the new method will be in scenarios, like a managed package, where the code needs to dynamically check if the required fields are populated and then populate them if required. I’ve written about this in http://www.fishofprey.com/2016/05/ive-just-found-my-new-favorite-apex.html
Thanks, Daniel. And, thank you for your excellent blog post!
d3jau nera tokio dalyko, kaip Microsoft &q&iu;antifanao"t#8230; tiesiog, kai kuri toki slamsta fanu nera:D comon… su tokiu biudzetu, koki turi Ms ju offisas turetu buti superine programa… kolkas nemokamas OpenOffice daro ji per visus galus… apie windowsus isvis net nesnekam geriau (nebent labai nori pasigirti, kad Win7 JAU "panasus" i zmoniska OS 😀 )… zdz, valio Microsoftui! 😀
hi Peter,
Great Post but isn’t that not similar to what we already have avail? found at stackexchange post from 2012 or so
with WHERE clause and LIMIT you can have this like SQL select * From….
String objectName = 'Contact'; // change the object here and
String query = 'SELECT ';
Map objectFields = Schema.getGlobalDescribe().get(objectName).getDescribe().fields.getMap();
Integer i = 0;
for(String s : objectFields.keySet()) {
if(i > 0){query += ', ';}
query += s;
i++;
}
query += ' FROM ' + objectName;
try {
List contacts= database.query(query); // change the object here
system.debug(groups[0]);
} catch (QueryException e){
//perform exception handling
}
Yes. It is a lot similar. The building of the SOQL query could be done like you have it. I think that the value in the new getMap function is being able to check if a field exists on the returned record before trying to access that field. For example, it could be useful for an ISV that declares a global interface that a subscriber implements that affects the record somehow (e.g. querying it) before it gets to the ISV code. The ISV code can use this method to verify that the field was actually queried and handle if it wasn’t. Daniel has a good post here http://www.fishofprey.com/2016/05/ive-just-found-my-new-favorite-apex.html too.
Hey I’ve made a helper method for use in triggers. It returns list of modified records for specified fields.
If you are doing a lot of this:
if ( a.name != trigger.oldMap.get( a.id ).name )
And want to simplify your code, check this link out: https://github.com/sjurgis/apex-getModifiedRecords