Skip to content

Instantly share code, notes, and snippets.

@mhamzas
Created April 17, 2023 07:09
Show Gist options
  • Save mhamzas/925a84cabcf8fdc4736ca93e334471d5 to your computer and use it in GitHub Desktop.
Save mhamzas/925a84cabcf8fdc4736ca93e334471d5 to your computer and use it in GitHub Desktop.
Helpful methods to get information about fields and objects, including schema information by Kevin Antonioli - https://live.playg.app/play/field-utils
This is by "Kevin Antonioli" publised on https://live.playg.app/play/field-utils
/**
* Author : Kevin Antonioli ([email protected])
* Description : Utility class for parsing information about fields and field paths
* Created : 03.27.2023
*
* Revisions:
* Date : Name : Notes
*/
public class FieldUtils {
// map to cache describes in for better performance:
private static Map<String, Schema.DescribeSObjectResult> objApiNameToDescribeMap = new Map<String, Schema.DescribeSObjectResult>();
/**
* @description: dynamically get the describe for a given object
* @param objApiName object api name to get the describe for
* @return describe the Schema.DescribeSObjectResult for the given object
*/
public static Schema.DescribeSObjectResult getDynamicDescribe(
String objApiName
) {
Schema.DescribeSObjectResult describe = objApiNameToDescribeMap.get(
objApiName
) == null
? ((SObject) Type.forName('Schema', objApiName).newInstance())
.getSObjectType()
.getDescribe()
: objApiNameToDescribeMap.get(objApiName);
objApiNameToDescribeMap.put(objApiName, describe);
return describe;
}
/**
* @description: given an SObject record, return a Schema.DescribeSObjectResult
* @param record SObject record to get the describe for
* @return describe the Schema.DescribeSObjectResult describe to return
*/
public static Schema.DescribeSObjectResult getDynamicDescribe(
SObject record
) {
return getDynamicDescribe(String.valueOf(record.getSObjectType()));
}
/**
* @description Given an SObject name, return a Schema.SObjectField field map.
* @param objApiName the name of the SObject in which to return a Schema.SObjectField field map for
* @return the Schema.SObjectField field map to return
*/
public static Map<String, Schema.SObjectField> getFieldMap(
String objApiName
) {
return getDynamicDescribe(objApiName).fields.getMap();
}
/**
* @description Given an SObject record, return a Schema.SObjectField field map.
* @param record SObject record to get the field map for
* @return the Schema.SObjectField field map to return
*/
public static Map<String, Schema.SObjectField> getFieldMap(SObject record) {
return getDynamicDescribe(record).fields.getMap();
}
/**
* @description: get picklist values for one of an object's picklist fields
* @param objApiName object api name
* @param fieldApiName picklist field api name
* @return options picklist values
*/
public static List<String> getPicklistLabels(
String objApiName,
String fieldApiName
) {
List<String> picklistValues = new List<String>();
Schema.DescribeFieldResult fieldResult = getFieldMap(objApiName)
.get(fieldApiName)
.getDescribe();
for (Schema.PicklistEntry plEntry : fieldResult.getPicklistValues()) {
picklistValues.add(plEntry.getLabel());
}
return picklistValues;
}
/**
* @description: get picklist values for one of an object's picklist fields
* @param objApiName object api name
* @param fieldApiName picklist field api name
* @return options picklist values
*/
public static List<String> getPicklistValues(
String objApiName,
String fieldApiName
) {
List<String> picklistValues = new List<String>();
Schema.DescribeFieldResult fieldResult = getFieldMap(objApiName)
.get(fieldApiName)
.getDescribe();
for (Schema.PicklistEntry plEntry : fieldResult.getPicklistValues()) {
picklistValues.add(plEntry.getValue());
}
return picklistValues;
}
/**
* @description -> Method to get properties of a field set.
* @usage example:
* List<Schema.FieldSetMember> fieldSetMemberList = readFieldSet('SBQQ__LineEditor','SBQQ__Quote__c');
* for(Schema.FieldSetMember fieldSetMemberObj : fieldSetMemberList) {
* system.debug('API Name ====>' + fieldSetMemberObj.getFieldPath()); //api name
* system.debug('Label ====>' + fieldSetMemberObj.getLabel());
* system.debug('Required ====>' + fieldSetMemberObj.getRequired());
* system.debug('DbRequired ====>' + fieldSetMemberObj.getDbRequired());
* system.debug('Type ====>' + fieldSetMemberObj.getType()); //type - STRING,PICKLIST
* }
*
* @param fieldSetName name of field set you wish to get the properties (ex: fields) for
* @param objApiName api name of object that field set resides on
* @return List<Schema.FieldSetMember -> field set properties to return.
*/
public static List<Schema.FieldSetMember> readFieldSet(
String fieldSetName,
String objApiName
) {
return getDynamicDescribe(objApiName)
.FieldSets.getMap()
.get(fieldSetName)
.getFields();
}
/**
* @description: get list of field API names to query, given a Schema.FieldSetMember list:
* @param fieldSetName name of field set you wish to get the properties (ex: fields) for
* @param objApiName api name of object that field set resides on
* @return objApiName a list of field API names parsed from the field set parameter
*/
public static List<String> getFieldSetFieldAPINames(
String fieldSetName,
String objApiName
) {
return getFieldSetFieldAPINames(readFieldSet(fieldSetName, objApiName));
}
/**
* @description: get list of field API names to query, given a Schema.FieldSetMember list:
* @param fieldSetMemberList a Schema.FieldSetMember list in which to parse the field API names from
* @return fieldSetFieldAPINames a list of field API names parsed from the field set parameter
*/
public static List<String> getFieldSetFieldAPINames(
List<Schema.FieldSetMember> fieldSetMemberList
) {
Set<String> fieldSetFieldAPINamesSet = new Set<String>();
for (Schema.FieldSetMember fieldSetMemberObj : fieldSetMemberList) {
fieldSetFieldAPINamesSet.add(fieldSetMemberObj.getFieldPath());
}
List<String> fieldSetFieldAPINames = new List<String>(
fieldSetFieldAPINamesSet
);
return fieldSetFieldAPINames;
}
/**
* @description: given an object api name and a field Api Name, determine if the field is createable
* @param objApiName object api name (ex: 'Account')
* @param fieldApiName field as string to check if it is createable (ex: 'Industry')
* @return Boolean whether or not the field is createable
*/
public static Boolean isFieldCreateable(
String objApiName,
String fieldApiName
) {
return getFieldMap(objApiName)
.get(fieldApiName)
.getDescribe()
.isCreateable();
}
/**
* @description: given an object api name and a field Api Name, determine if the field is accessible
* @param objApiName object api name (ex: 'Account')
* @param fieldApiName field as string to check if it is accessible (ex: 'Industry')
* @return Boolean whether or not the field is accessible
*/
public static Boolean isFieldAccessible(
String objApiName,
String fieldApiName
) {
return getFieldMap(objApiName)
.get(fieldApiName)
.getDescribe()
.isAccessible();
}
/**
* @description: given an object api name and a field Api Name, determine if the field is updateable
* @param objApiName object api name (ex: 'Account')
* @param fieldApiName field as string to check if it is updateable (ex: 'Industry')
* @return Boolean whether or not the field is updateable
*/
public static Boolean isFieldUpdateable(
String objApiName,
String fieldApiName
) {
return getFieldMap(objApiName)
.get(fieldApiName)
.getDescribe()
.isUpdateable();
}
/**
* @description: given an object api name and a field Api Name, determine the field type
* @param objApiName object api name (ex: 'Account')
* @param fieldApiName field as string to check if it is updateable (ex: 'Industry')
* @return String the field type (ex: 'PICKLIST')
*/
public static String getFieldType(String objApiName, String fieldApiName) {
return String.valueOf(
getFieldMap(objApiName).get(fieldApiName).getDescribe().getType()
);
}
/**
* @description Get all fields for an sobject as a list - helpful for dynamic SOQL
* @param objApiName : the object name to get the fields for
* @return allFields : of all fields for the object;
*/
public static List<String> getAllFieldsForSobj(String objApiName) {
List<String> allFields = new List<String>(getFieldMap(objApiName).keySet());
return allFields;
}
/**
* @description Get all fields for an sobject as a comma-delimited string - helpful for dynamic SOQL
* @param objApiName : the object name to get the fields for
* @return String : comma delimited string of all fields for the object delimited by commas
*/
public static String getAllFieldsForSObjAsStr(String objApiName) {
return String.join(getAllFieldsForSobj(objApiName), ', ');
}
/**
* Method to return list of creatable fields for a given object.
* @param String objApiName
* @return List of creatable fields for a given SObject.
*/
public static List<String> getCreatableFields(String objApiName) {
List<String> creatableFields = new List<String>();
for (Schema.SObjectField field : getFieldMap(objApiName).values()) {
// field is updateable
Schema.DescribeFieldResult fieldDescribe = field.getDescribe(); // describe each field (fd)
if (fieldDescribe.isCreateable()) {
creatableFields.add(fieldDescribe.getName());
}
}
return creatableFields;
}
/**
* Method to return list of accessible fields for a given object.
* @param String objApiName
* @return List of accessible fields for a given SObject.
*/
public static List<String> getAccessibleFields(String objApiName) {
List<String> accessibleFields = new List<String>();
for (Schema.SObjectField field : getFieldMap(objApiName).values()) {
Schema.DescribeFieldResult fieldDescribe = field.getDescribe(); // describe each field (fd)
if (fieldDescribe.isAccessible()) {
accessibleFields.add(fieldDescribe.getName());
}
}
return accessibleFields;
}
/**
* @description: get all updateable fields for an sobject as a list
* @param objApiName API name of SObject to get updateable fields for
* @return updateableFields List of updateable fields for a given SObject.
*/
public static List<String> getUpdateableFields(String objApiName) {
List<String> updateableFields = new List<String>();
for (Schema.SObjectField field : getFieldMap(objApiName).values()) {
Schema.DescribeFieldResult fieldDescribe = field.getDescribe(); // describe each field (fd)
if (fieldDescribe.isUpdateable()) {
updateableFields.add(fieldDescribe.getName());
}
}
return updateableFields;
}
/**
* @description Get all creatable fields for an sobject as a comma-delimited string - helpful for dynamic SOQL
* @param objApiName : the object name to get the fields for
* @return String : comma delimited string of all creatable fields for the object delimited by commas
*/
public static String getAllCreatableFieldsAsStr(String objApiName) {
return String.join(getCreatableFields(objApiName), ', ');
}
/**
* @description get all getAccessibleFields fields for an sobject as a comma-delimited string - helpful for dynamic SOQL
* @param objApiName : the object name to get the fields for
* @return String : comma delimited string of all getAccessibleFields fields for the object delimited by commas
*/
public static String getAllAccessibleFieldsAsStr(String objApiName) {
return String.join(getAccessibleFields(objApiName), ', ');
}
/**
* @description get all updateable fields for an sobject as a comma-delimited string - helpful for dynamic SOQL
* @param objApiName : the object name to get the fields for
* @return String : comma delimited string of all updateable fields for the object delimited by commas
*/
public static String getAllUpdateableFieldsAsStr(String objApiName) {
return String.join(getUpdateableFields(objApiName), ', ');
}
/**
* @description Get all fields for an sobject as a list, except those in the blacklist
* @param objApiName : the object name to get the fields for
* @param blackList : a list of fields to exclude
* @return List<String> : a list of all fields (except blacklist) for an SObject
*/
public static List<String> getAllFieldsExceptBlacklist(
String objApiName,
List<String> blackList
) {
Set<string> fields = new Set<String>(getAllFieldsForSobj(objApiName));
for (String blackListedField : blackList) {
if (fields.contains(blackListedField)) {
fields.remove(blackListedField);
} else if (fields.contains(blackListedField.toLowerCase())) {
fields.remove(blackListedField.toLowerCase());
}
}
return new List<String>(fields);
}
/**
* @description Get all fields (except blacklist) for an sobject as a comma-delimited string - helpful for dynamic SOQL
* @param objApiName : the SOBbject name to get the fields for
* @param blackList : a list of fields to exclude
* @return String : comma delimited string of all fields for the SObject (except blacklist)
*/
public static String getAllFieldsExceptBlacklistAsStr(
String objApiName,
List<String> blackList
) {
return String.join(
getAllFieldsExceptBlacklist(objApiName, blackList),
', '
);
}
/**
* @description, for a given record and field path, parse the last sub object from the path
* For example: for an Contact record and Account.Owner.Name path, parse the User (Owner) record from the path
* @param record record to parse last sub object from
* @param fieldpath the field path to use in the parsing (ex: Account.Owner.Name)
*/
public static SObject parseLastSubObjectFromPath(
SObject record,
String fieldPath
) {
SObject tempObj = record;
String objsOnly = fieldPath.substringBeforeLast('.');
for (String obj : objsOnly.split('\\.')) {
try {
tempObj = (SObject) tempObj.getSobject(obj);
} catch (Exception e) {
return null;
}
}
return tempObj;
}
/**
* @description: method to parse field values from field references, even if in dot notation (ex: Account.Owner.Name)
* @usage: if I have a queried Task record where I queried Account.Owner.Name from the Task,
* if I pass in the Task record and 'Contract.Account.Name', this method will return the value stored in the 'Name'.
* Useful for when SObjectRecord.get(field) falls short since it can't do SObjectRecord.get(relationshipField.Field)
* @param record : the record in which to parse the field reference from
* @param fieldPath : the field reference in which to parse. Ex: 'Account.Owner.Name' will get the Name field value parsed
* @return fieldVal : the String, Integer, Boolean, etc parsed value.
*/
public static Object parseValueFromFieldPath(
SObject record,
String fieldPath
) {
SObject tempObj = record;
Object fieldVal;
try {
// If provided field is using dot notation, get nested object and field
if (!fieldPath.contains('.')) {
return record.get(fieldPath);
} else {
// ex: Account.Owner.Name, loop through Account.Owner
for (
String pathSegment : fieldPath.substringBeforeLast('.').split('\\.')
) {
// dynamically accommodate for when path contains multiple nested SObjects (ex: Account.Owner.Name)
tempObj = (SObject) tempObj.getSobject(pathSegment);
}
String fieldApiName = fieldPath.substringAfterLast('.');
fieldVal = tempObj.get(fieldApiName);
}
} catch (Exception e) {
return null;
}
return fieldVal;
}
}
/**
* Author : Kevin Antonioli ([email protected])
* Description : Provides test code coverage for FieldUtils.cls
* Created : 03.29.2023
*
* Revisions:
* Date : Name : Notes
*/
@isTest
private class FieldUtilsTest {
@isTest
static void test_getDynamicDescribe() {
List<Account> accounts = createTestAccounts();
String accountNumberLabel2 = FieldUtils.getDynamicDescribe(accounts[0])
.fields.getMap()
.get('AccountNumber')
.getDescribe()
.getLabel();
Assert.areEqual(
accountNumberLabel2,
'Account Number',
'Expected accountNumberLabel2 to be "Account Number"'
);
String accountNumberLabel = FieldUtils.getDynamicDescribe('Account')
.fields.getMap()
.get('AccountNumber')
.getDescribe()
.getLabel();
Assert.areEqual(
accountNumberLabel,
'Account Number',
'Expected accountNumberLabel to be "Account Number"'
);
}
@isTest
static void test_getFieldMap() {
List<Account> accounts = createTestAccounts();
String accountNumberLabel = FieldUtils.getFieldMap(accounts[0])
.get('AccountNumber')
.getDescribe()
.getLabel();
Assert.areEqual(
accountNumberLabel,
'Account Number',
'Expected accountNumberLabel to be "Account Number"'
);
String accountNumberLabel2 = FieldUtils.getFieldMap('Account')
.get('AccountNumber')
.getDescribe()
.getLabel();
Assert.areEqual(
accountNumberLabel2,
'Account Number',
'Expected accountNumberLabel2 to be "Account Number"'
);
}
@isTest
static void test_parseValueFromFieldPath() {
List<Account> accounts = createTestAccounts();
List<Contact> contactList = new List<Contact>();
contactList.add(createTestContact(accounts[0]));
insert contactList;
Contact queriedContact = [
SELECT Id, Name, Account.Name
FROM Contact
WHERE Id = :contactList[0].Id
LIMIT 1
];
Test.startTest();
String accountName = (String) FieldUtils.parseValueFromFieldPath(
queriedContact,
'Account.Name'
);
String contactName = (String) FieldUtils.parseValueFromFieldPath(
queriedContact,
'Name'
);
String blah = (String) FieldUtils.parseValueFromFieldPath(
queriedContact,
'BLAH'
);
Test.stopTest();
Assert.areEqual(
accountName,
queriedContact.Account.Name,
'Expected accountName to be equal to the queried contact\'s account name'
);
Assert.areEqual(
contactName,
queriedContact.Name,
'Expected contactName to be equal to the queried contact\'s name'
);
Assert.isNull(blah, 'Expected blah to be null');
}
@isTest
static void test_parseLastSubObjectFromPath() {
List<Account> accounts = createTestAccounts();
accounts[0].OwnerId = UserInfo.getUserId();
update accounts;
List<Contact> contactList = new List<Contact>();
contactList.add(createTestContact(accounts[0]));
insert contactList;
Contact contact = [
SELECT Id, Account.Owner.Name
FROM Contact
WHERE Id IN :contactList
];
SObject obj = FieldUtils.parseLastSubObjectFromPath(
contact,
'Account.Owner.Name'
);
Assert.isTrue(obj instanceof User);
SObject obj2 = FieldUtils.parseLastSubObjectFromPath(
contact,
'Account.BLAH.name'
);
Assert.isNull(obj2, 'Expected obj2 to be null');
}
@isTest
static void test_picklistMethods() {
Assert.isTrue(
FieldUtils.getPicklistLabels('Contact', 'LeadSource').contains('Web')
);
Assert.isTrue(
FieldUtils.getPicklistValues('Contact', 'LeadSource').contains('Web')
);
}
@isTest
static void test_fieldSetMethods() {
try {
// cannot create field set in test class; this is the best we can do:
List<Schema.FieldSetMember> fieldSet = FieldUtils.readFieldSet(
'some_field_set',
'Account'
);
} catch (exception e) {
Assert.isTrue(
e.getMessage().contains('Attempt to de-reference a null object')
);
}
try {
// cannot create field set in test class; this is the best we can do:
List<String> fieldApiNames = FieldUtils.getFieldSetFieldAPINames(
'some_field_set',
'Account'
);
} catch (exception e) {
Assert.isTrue(
e.getMessage().contains('Attempt to de-reference a null object')
);
}
try {
// cannot create field set in test class; this is the best we can do:
List<String> fieldApiNames = FieldUtils.getFieldSetFieldAPINames(null);
} catch (exception e) {
Assert.isTrue(
e.getMessage().contains('Attempt to de-reference a null object')
);
}
}
@isTest
static void test_fieldPermissionMethods() {
Assert.isTrue(
FieldUtils.isFieldUpdateable('Account', 'Industry'),
'Expected isFieldUpdateble to be true for Account Industry field'
);
Assert.isTrue(
FieldUtils.isFieldAccessible('Account', 'Industry'),
'Expected isFieldAccessible to be true for Account Industry field'
);
Assert.isTrue(
FieldUtils.isFieldCreateable('Account', 'Name'),
'Expected isFieldCreateable to be true for Account Name field'
);
Assert.isFalse(
FieldUtils.isFieldUpdateable('Account', 'CreatedDate'),
'Expected isFieldUpdateble to be false for Created Date field'
);
Assert.isTrue(
FieldUtils.isFieldAccessible('Account', 'CreatedDate'),
'Expected isFieldAccessible to be true for Created Date field'
);
Assert.isFalse(
FieldUtils.isFieldCreateable('Account', 'CreatedDate'),
'Expected isFieldCreateable to be false for Created Date field'
);
}
@isTest
static void test_getFieldType() {
Assert.areEqual(FieldUtils.getFieldType('Account', 'Industry'), 'PICKLIST');
}
@isTest
static void test_GetFieldsMethods() {
Assert.isTrue(FieldUtils.getAllFieldsForSobj('Account').size() > 0);
Assert.isTrue(FieldUtils.getAllFieldsForSObjAsStr('Account').length() > 0);
Assert.isFalse(
FieldUtils.getAllFieldsExceptBlacklist(
'Account',
new List<String>{ 'Name' }
)
.contains('name')
);
Assert.isTrue(
FieldUtils.getAllFieldsExceptBlacklist(
'Account',
new List<String>{ 'Name' }
)
.contains('type')
);
Assert.isFalse(
FieldUtils.getAllFieldsExceptBlacklistAsStr(
'Account',
new List<String>{ 'Name' }
)
.contains('Name,')
);
Assert.isTrue(
FieldUtils.getAllFieldsExceptBlacklistAsStr(
'Account',
new List<String>{ 'Name' }
)
.contains('type,')
);
Assert.isTrue(
FieldUtils.getAllCreatableFieldsAsStr('Account').length() > 0,
'Expected the length of Account createable fields string to be greater than 0'
);
Assert.isTrue(
FieldUtils.getAllCreatableFieldsAsStr('Account').contains('Name'),
'Expected the Account createable fields string to contain "Name"'
);
Assert.isFalse(
FieldUtils.getAllCreatableFieldsAsStr('Account').contains('CreatedById'),
'Expected the Account createable fields string to contain "CreatedById"'
);
Assert.isTrue(
FieldUtils.getAllUpdateableFieldsAsStr('Account').length() > 0,
'Expected the length of Account updateable fields string to be greater than 0'
);
Assert.isTrue(
FieldUtils.getAllUpdateableFieldsAsStr('Account').contains('Name'),
'Expected the Account updateable fields string to contain "Name"'
);
Assert.isFalse(
FieldUtils.getAllUpdateableFieldsAsStr('Account').contains('CreatedById'),
'Expected the Account updateable fields string to contain "CreatedById"'
);
Assert.isTrue(
FieldUtils.getAllAccessibleFieldsAsStr('Account').length() > 0,
'Expected the length of Account accessible fields string to be greater than 0'
);
Assert.isTrue(
FieldUtils.getAllAccessibleFieldsAsStr('Account').contains('Name'),
'Expected the Account accessible fields string to contain "Name"'
);
Assert.isTrue(
FieldUtils.getAllAccessibleFieldsAsStr('Account').contains('CreatedById'),
'Expected the Account accessible fields string to contain "CreatedById"'
);
}
/** HELPER METHODS */
private static List<Account> createTestAccounts() {
List<Account> accounts = new List<Account>();
accounts.add(new Account(Name = '1'));
accounts.add(new Account(Name = '2'));
accounts.add(new Account(Name = '3'));
insert accounts;
return accounts;
}
private static Contact createTestContact(Account account) {
return new Contact(
FirstName = account.Name,
LastName = account.name,
AccountId = account.Id
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment