Simplifying Data Transformation to Maps with CollectionUtils Class in Salesforce

Simplifying Data Transformation to Maps with CollectionUtils Class in Salesforce

Introduction:

Welcome to our new post of “Simplifying Data Transformation with CollectionUtils Class in Salesforce”.

Are you tired of writing countless for loops just to transform a list of sObjects into a map? Do you want a generic, reusable, and type-safe solution to your data transformation needs? If so, you’re in luck! The CollectionUtils library class is here to help.

The CollectionUtils library class offers a collection of generic methods. These methods are type-safe and will revolutionize the way you work with your Salesforce data.

In this blog post, we will see how to use the CollectionUtils library class for creating maps. This approach will prevent developers from writing countless for loops. It helps transform a list of sobjects into a map and save time.

Code Explanation:

The library provides three main methods: idMapFromCollectionByKey, stringMapFromCollectionByKey, and idMapFromCollectionByKeyForList. These methods allow you to transform your data with ease, giving you more time to focus on what really matters.

  • idMapFromCollectionByKey
    1. The idMapFromCollectionByKey method is a generic, reusable, and type-safe method for generating a Map<Id, Some_SObject> from a list.
    2. This method stops developers from writing countless for loops. It also helps transform a list of SObjects. In the transformation, the map’s key is something other than the Id field.
    3. This method maintains type safety by accepting a generic list of SObjects. It determines the concrete SObject type of the incoming list’s first object. The concretely typed map is cast to a Map<Id, SObject>, and generic SObject methods construct the map.
  • stringMapFromCollectionByKey
    1. The stringMapFromCollectionByKey method functions similarly to the idMapFromCollectionByKey method but generates a Map<String, Some_SObject> from a list.
    2. The key parameter must be something castable to a string. Developers are responsible for ensuring the uniqueness of the key’s value when using this method.
  • idMapFromCollectionByKeyForList
    1. The idMapFromCollectionByKeyForList method accepts an incoming List of sObjects and generates a Map<Id, List<sObject>>.
    2. This method helps avoid cluttering the codebase with for loops. For example, it can take a list of Contacts and return a Map<AccountId, List<Contact>>.
  • stringMapFromCollectionByKeyForList
    1. The stringMapFromCollectionByKeyForList method functions similarly to the idMapFromCollectionByKeyForList but returns a Map<String, List<sObject>>.
    2. It groups the SObjects into lists. Each key is derived from a string-castable field. The developer must ensure key uniqueness.
Internal Logic and Helpers
Input Validation

All public methods first call isInvalidInput to check if the key is blank or the list is null/empty. If so, they return an empty Map.

Dynamic Map Creation
  • Uses Type.forName and newInstance() to create Maps of the correct type at runtime, ensuring type safety.
  • If dynamic creation fails, falls back to a generic Map.
Field Value Extraction
  • Uses getFieldValue to extract the value for the key field from each SObject.
  • Supports one level of nesting (e.g., 'Account.OwnerId').
Map Population
  • For single-value Maps, each key maps to one SObject (last one wins if duplicates).
  • For list-value Maps, each key maps to a list of SObjects sharing that key.
Example: How a Method Works

Let’s look at idMapFromCollectionByKey:

public static Map<Id, SObject> idMapFromCollectionByKey(String key, List<SObject> incomingList) {
    if (isInvalidInput(key, incomingList)) {
        return new Map<Id, SObject>();
    }
    return generateIdMap(key, incomingList);
}
  • Checks input.
  • Calls generateIdMap, which:
    • Dynamically creates a Map<Id, SObjectType>.
    • Loops through the list, gets the key value using getFieldValue.
    • Puts the SObject into the Map using the key.
Nested Field Support

The getFieldValue method allows you to use keys like 'Account.OwnerId'. It splits the key by ., gets the parent SObject, then the child field.

Sample Code:

Here is the sample CollectionUtils class –

/**
* CollectionUtils
* ———————-
* @description
* A library of generic, type-safe collection methods for transforming and working with
* lists of SObjects. This utility is designed to simplify the conversion of SObject collections
* into keyed Maps, supporting nested field access and maintaining type safety.
*
* Key Features:
* – Converts lists into Maps using specified fields as keys.
* – Supports both `Id` and `String` field types for keys.
* – Handles nested fields with one-level parent-child relationships.
* – Offers methods to create Maps with single SObject values or lists of SObjects per key.
*
* Designed for use in Lightning and Salesforce development to streamline data transformations.
*/
public with sharing class CollectionUtils {
/**
* @description Converts a list of SObjects into a Map where the key is derived from a specified `Id` field.
*
* @param key The field name to use as the Map key. Must be of type `Id`.
* @param incomingList A list of SObjects to transform into the Map.
* @return A Map<Id, SObject> where the key is derived from the specified field.
* @example Id key from same object
* List<Contact> contacts = [SELECT AccountId, FirstName, LastName FROM Contact];
* Map<Id, Contact> contactsByAccountId = (Map<Id, Contact>)
* CollectionUtils.idMapFromCollectionByKey('AccountId', contacts);
*/
public static Map<Id, SObject> idMapFromCollectionByKey(String key, List<SObject> incomingList) {
// Check if the key is blank or the incoming list is null or empty
if (isInvalidInput(key, incomingList)) {
// Return an empty Map if the input is invalid
return new Map<Id, SObject>();
}
// Generate the Map using the specified key
return generateIdMap(key, incomingList);
}
/**
* @description Similar to `idMapFromCollectionByKey` but uses String keys.
* Useful for fields not of type Id.
* @param key The field name to use as the Map key. Must be castable to `String`.
* @param incomingList A list of SObjects to transform into the Map.
* @return A Map<String, SObject> where the key is derived from the specified field.
* @example key from same object
* Contact[] contacts = [SELECT id,firstName, lastName,email FROM Contact LIMIT 50];
* Map<String, Contact> contactsByEmail = (Map<String, Contact>)
* CollectionUtils.stringMapFromCollectionByKey('email', contacts);
*/
public static Map<String, SObject> stringMapFromCollectionByKey(String key, List<SObject> incomingList) {
// Check if the key is blank or the incoming list is null or empty
if (isInvalidInput(key, incomingList)) {
// Return an empty Map if the input is invalid
return new Map<String, SObject>();
}
// Generate the Map using the specified key
return generateStringMap(key, incomingList);
}
/**
* @description Creates a Map where each key maps to a list of SObjects. The key is an `Id` field.
*
* @param key The field name (or nested field) to use as the Map key.
* @param incomingList A list of SObjects to transform into the Map.
* @return A Map<Id, List<SObject>> where each key maps to a list of related SObjects.
* @example
* Contact[] contacts = [SELECT AccountId, firstName, lastName FROM Contact LIMIT 50];
* Map<Id, List<Contact>> contactsByAccountId = (Map<Id, List<Contact>>)
CollectionUtils.idMapFromCollectionByKeyForList ('AccountId', contacts);
* @example Id key from from diffrent related object only single level
* Contact[] contacts = [SELECT Account.OwnerId, firstName, lastName FROM Contact LIMIT 50];
* Map<Id, List<Contact>> contactsByAccountOwnerId = (Map<Id, List<Contact>>)
* CollectionUtils.idMapFromCollectionByKeyForList('Account.OwnerId', contacts);
*/
public static Map<Id, List<SObject>> idMapFromCollectionByKeyForList(String key, List<SObject> incomingList) {
// Check if the key is blank or the incoming list is null or empty
if (isInvalidInput(key, incomingList)) {
// Return an empty Map if the input is invalid
return new Map<Id, List<SObject>>();
}
// Generate the Map using the specified key
return generateIdMapForList(key, incomingList);
}
/**
* @description Similar to `idMapFromCollectionByKeyForList` but uses `String` keys.
*
* @param key The field name (or nested field) to use as the Map key.
* @param incomingList A list of SObjects to transform into the Map.
* @return A Map<String, List<SObject>> where each key maps to a list of related SObjects.
* @example
* Opportunity[] opportunities = [SELECT StageName, Amount FROM Opportunity];
* Map<String, List<Opportunity>> opportunitiesByStage = (Map<String, List<Opportunity>>)
* CollectionUtils.stringMapFromCollectionByKeyForList('StageName', opportunities);
* @example String key from from diffrent related object only single level
* Contact[] contacts = [SELECT Account.Name, firstName, lastName FROM Contact LIMIT 50];
* Map<String, List<Contact>> contactsByAccountName = (Map<String, List<Contact>>)
* CollectionUtils.stringMapFromCollectionByKeyForList('Account.Name', contacts);
*/
public static Map<String, List<SObject>> stringMapFromCollectionByKeyForList(String key, List<SObject> incomingList) {
// Check if the key is blank or the incoming list is null or empty
if (isInvalidInput(key, incomingList)) {
// Return an empty Map if the input is invalid
return new Map<String, List<SObject>>();
}
// Generate the Map using the specified key
return generateStringMapForList(key, incomingList);
}
/**
* @description Checks if the input parameters are valid.
*
* @param key The field name to use as the Map key.
* @param incomingList A list of SObjects to transform into the Map.
* @return true if the input is invalid, false otherwise.
*/
private static Boolean isInvalidInput(String key, List<SObject> incomingList) {
// Check if the key is blank or the incoming list is null or empty
return String.isBlank(key) || incomingList == null || incomingList.isEmpty();
}
/**
* @description Generates a Map where each key maps to a single SObject. The key is an Id field.
*
* @param key The field name (or nested field) to use as the Map key.
* @param incomingList A list of SObjects to transform into the Map.
* @return A Map<Id, SObject> where each key maps to a single SObject.
*/
private static Map<Id, SObject> generateIdMap(String key, List<SObject> incomingList) {
// Initialize the return value
Map<Id, SObject> returnValues;
try {
// Get the SObject type of the list dynamically
String objType = getSobjectTypeFromList(incomingList);
// Define a dynamic Map type based on the SObject type
Type dynamicMapType = Type.forName('Map<Id,' + objType + '>');
// Create a new instance of the dynamic Map type
// This allows for type-safe instantiation of the Map
// and avoids the need for hardcoding the SObject type.
returnValues = (Map<Id, SObject>) dynamicMapType.newInstance();
// Populate the Map with key-value pairs.
for (SObject current : incomingList) {
// Get the value of the specified field
Id keyValue = (Id) getFieldValue(current, key);
// Check if the key value is not null
if (keyValue != null) {
// Add the current SObject to the Map using the key value
returnValues.put(keyValue, current);
}
}
} catch (Exception ex) {
// Fallback to generic map if dynamic type fails
returnValues = new Map<Id, SObject>();
}
return returnValues;
}
/**
* @description Generates a Map where each key maps to a single SObject. The key is a String field.
*
* @param key The field name (or nested field) to use as the Map key.
* @param incomingList A list of SObjects to transform into the Map.
* @return A Map<String, SObject> where each key maps to a single SObject.
*/
private static Map<String, SObject> generateStringMap(String key, List<SObject> incomingList) {
// Initialize the return value
Map<String, SObject> returnValues;
try {
// Get the SObject type of the list dynamically
String objType = getSobjectTypeFromList(incomingList);
// Define a dynamic Map type based on the SObject type
Type dynamicMapType = Type.forName('Map<String,' + objType + '>');
// Create a new instance of the dynamic Map type
// This allows for type-safe instantiation of the Map
// and avoids the need for hardcoding the SObject type.
returnValues = (Map<String, SObject>) dynamicMapType.newInstance();
// Populate the Map with key-value pairs.
for (SObject current : incomingList) {
// Get the value of the specified field
String keyValue = String.valueOf(getFieldValue(current, key));
// Check if the key value is not null or blank
if (!String.isBlank(keyValue)) {
// Add the current SObject to the Map using the key value
returnValues.put(keyValue, current);
}
}
} catch (Exception ex) {
// Fallback to generic map if dynamic type fails
returnValues = new Map<String, SObject>();
}
return returnValues;
}
/**
* @description Generates a Map where each key maps to a list of SObjects. The key is an Id field.
*
* @param key The field name (or nested field) to use as the Map key.
* @param incomingList A list of SObjects to transform into the Map.
* @return A Map<Id, List<SObject>> where each key maps to a list of related SObjects.
*/
private static Map<Id, List<SObject>> generateIdMapForList(String key, List<SObject> incomingList) {
// Initialize the return value
Map<Id, List<SObject>> returnValues;
try {
// Get the SObject type of the list dynamically
String objType = getSobjectTypeFromList(incomingList);
// Define a dynamic Map type based on the SObject type
Type dynamicMapType = Type.forName('Map<Id, List<' + objType + '>>');
// Create a new instance of the dynamic Map type
// This allows for type-safe instantiation of the Map
// and avoids the need for hardcoding the SObject type.
returnValues = (Map<Id, List<SObject>>) dynamicMapType.newInstance();
// Populate the Map with key-value pairs.
for (SObject current : incomingList) {
// Get the value of the specified field
Id keyValue = (Id) getFieldValue(current, key);
// Check if the key value is not null
if (keyValue != null) {
// Add the current SObject to the Map using the key value
if (!returnValues.containsKey(keyValue)) {
// Initialize the list if the key does not exist
returnValues.put(keyValue, new List<SObject>());
}
// Add the current SObject to the list for the key
returnValues.get(keyValue).add(current);
}
}
} catch (Exception ex) {
// Fallback to generic map if dynamic type fails
returnValues = new Map<Id, List<SObject>>();
}
return returnValues;
}
/**
* @description Generates a Map where each key maps to a list of SObjects. The key is a String field.
*
* @param key The field name (or nested field) to use as the Map key.
* @param incomingList A list of SObjects to transform into the Map.
* @return A Map<String, List<SObject>> where each key maps to a list of related SObjects.
*/
private static Map<String, List<SObject>> generateStringMapForList(String key, List<SObject> incomingList) {
// Initialize the return value
Map<String, List<SObject>> returnValues;
try {
// Get the SObject type of the list dynamically
String objType = getSobjectTypeFromList(incomingList);
// Define a dynamic Map type based on the SObject type
Type dynamicMapType = Type.forName('Map<String, List<' + objType + '>>');
// Create a new instance of the dynamic Map type
// This allows for type-safe instantiation of the Map
// and avoids the need for hardcoding the SObject type.
returnValues = (Map<String, List<SObject>>) dynamicMapType.newInstance();
// Populate the Map with key-value pairs.
for (SObject current : incomingList) {
// Get the value of the specified field
String keyValue = String.valueOf(getFieldValue(current, key));
// Check if the key value is not null or blank
if (!String.isBlank(keyValue)) {
// Add the current SObject to the Map using the key value
if (!returnValues.containsKey(keyValue)) {
// Initialize the list if the key does not exist
returnValues.put(keyValue, new List<SObject>());
}
// Add the current SObject to the list for the key
returnValues.get(keyValue).add(current);
}
}
} catch (Exception ex) {
// Fallback to generic map if dynamic type fails
returnValues = new Map<String, List<SObject>>();
}
return returnValues;
}
/**
* @description Retrieves the SObject type of the first item in a list.
*
* @param incomingList The list of SObjects.
* @return The SObject type as a String.
*/
@TestVisible
private static String getSobjectTypeFromList(List<SObject> incomingList) {
return (!incomingList.isEmpty()) ? String.valueOf(incomingList[0]?.getSObjectType()) : 'sObject';
}
/**
* @description Helper to get a (possibly nested) field value from an SObject.
* Handles one level of nesting (e.g., 'Account.OwnerId').
*
* @param sobj The SObject to extract the value from.
* @param key The field name, possibly nested (dot notation).
* @return The value of the field, or null if not found.
*/
private static Object getFieldValue(SObject sobj, String key) {
// Check if the SObject is null or the key is blank
if (sobj == null || String.isBlank(key)) {return null;}
// Split the key into parts to handle nested fields
List<String> fieldParts = key.split('\\.');
// Check if the key has one or two parts
if (fieldParts.size() == 1) {
// If only one part, return the value directly from the SObject
return sobj.get(key);
} else if (fieldParts.size() == 2) {
// If two parts, get the parent SObject and return the nested field value
SObject parent = sobj.getSObject(fieldParts[0]);
// Check if the parent SObject is not null
return parent != null ? parent.get(fieldParts[1]) : null;
}
// If the key has more than two parts, return null
// Only one level of nesting supported
return null;
}
}
/**
* @description
* Test class for CollectionUtils.
* Verifies all functionality related to converting SObject collections to maps with various key types.
* Covers both direct and nested field mapping, as well as empty, null, and error/exception cases.
*/
@isTest
public class CollectionUtils_Test {
/**
* @testSetup
* Creates test Accounts and Contacts for use in all test methods.
* Ensures a consistent dataset for map operations, including cases with and without AccountId/Email.
*/
@testSetup
static void setup() {
// Create Accounts for relationship testing
List<Account> accounts = new List<Account>{
new Account(Name = 'Acme'),
new Account(Name = 'Beta'),
new Account(Name = 'Gamma')
};
insert accounts;
// Create Contacts, some with AccountId and Email, one without either for edge case testing
List<Contact> contacts = new List<Contact>{
new Contact(FirstName = 'John', LastName = 'Doe', Email = 'john@acme.com', AccountId = accounts[0].Id),
new Contact(FirstName = 'Jane', LastName = 'Smith', Email = 'jane@beta.com', AccountId = accounts[1].Id),
new Contact(FirstName = 'Jim', LastName = 'Beam', Email = 'jim@gamma.com', AccountId = accounts[2].Id),
new Contact(FirstName = 'NoAccount', LastName = 'NoEmail') // No AccountId or Email
};
insert contacts;
}
/**
* Tests mapping Contacts by AccountId using idMapFromCollectionByKey.
* Verifies that each AccountId maps to the correct Contact.
*/
@isTest
static void testIdMapFromCollectionByKey() {
List<Contact> contacts = [SELECT Id, AccountId FROM Contact WHERE Email != null];
Map<Id, SObject> mapResult = CollectionUtils.idMapFromCollectionByKey('AccountId', contacts);
System.assertEquals(3, mapResult.size(), 'Should map each AccountId to a Contact');
for (Contact c : contacts) {
// Each AccountId should map to the correct Contact
System.assertEquals(c.Id, ((Contact)mapResult.get(c.AccountId)).Id, 'Contact should match AccountId');
}
}
/**
* Tests idMapFromCollectionByKey with an empty list.
* Expects an empty map as a result.
*/
@isTest
static void testIdMapFromCollectionByKey_EmptyList() {
Map<Id, SObject> mapResult = CollectionUtils.idMapFromCollectionByKey('AccountId', new List<SObject>());
System.assertEquals(0, mapResult.size(), 'Empty input should return empty map');
}
/**
* Tests idMapFromCollectionByKey with a null list.
* Expects an empty map as a result.
*/
@isTest
static void testIdMapFromCollectionByKey_NullList() {
Map<Id, SObject> mapResult = CollectionUtils.idMapFromCollectionByKey('AccountId', null);
System.assertEquals(0, mapResult.size(), 'Null input should return empty map');
}
/**
* Tests idMapFromCollectionByKey with a blank key.
* Expects an empty map as a result.
*/
@isTest
static void testIdMapFromCollectionByKey_BlankKey() {
List<Contact> contacts = [SELECT Id, AccountId FROM Contact];
Map<Id, SObject> mapResult = CollectionUtils.idMapFromCollectionByKey('', contacts);
System.assertEquals(0, mapResult.size(), 'Blank key should return empty map');
}
/**
* Tests idMapFromCollectionByKey with an invalid key.
* Expects an empty map due to exception handling.
*/
@isTest
static void testIdMapFromCollectionByKey_InvalidKey() {
List<Contact> contacts = [SELECT Id, AccountId FROM Contact];
// Should not throw, should return empty map due to exception handling
Map<Id, SObject> mapResult = CollectionUtils.idMapFromCollectionByKey('NonExistentField', contacts);
System.assertEquals(0, mapResult.size(), 'Invalid key should return empty map');
}
/**
* Tests mapping Contacts by Email using stringMapFromCollectionByKey.
* Verifies that each Email maps to the correct Contact.
*/
@isTest
static void testStringMapFromCollectionByKey() {
List<Contact> contacts = [SELECT Id, Email FROM Contact WHERE Email != null];
Map<String, SObject> mapResult = CollectionUtils.stringMapFromCollectionByKey('Email', contacts);
System.assertEquals(3, mapResult.size(), 'Should map each Email to a Contact');
for (Contact c : contacts) {
// Each Email should map to the correct Contact
System.assertEquals(c.Id, ((Contact)mapResult.get(c.Email)).Id, 'Contact should match Email');
}
}
/**
* Tests stringMapFromCollectionByKey with an empty list.
* Expects an empty map as a result.
*/
@isTest
static void testStringMapFromCollectionByKey_EmptyList() {
Map<String, SObject> mapResult = CollectionUtils.stringMapFromCollectionByKey('Email', new List<SObject>());
System.assertEquals(0, mapResult.size(), 'Empty input should return empty map');
}
/**
* Tests stringMapFromCollectionByKey with a null list.
* Expects an empty map as a result.
*/
@isTest
static void testStringMapFromCollectionByKey_NullList() {
Map<String, SObject> mapResult = CollectionUtils.stringMapFromCollectionByKey('Email', null);
System.assertEquals(0, mapResult.size(), 'Null input should return empty map');
}
/**
* Tests stringMapFromCollectionByKey with a blank key.
* Expects an empty map as a result.
*/
@isTest
static void testStringMapFromCollectionByKey_BlankKey() {
List<Contact> contacts = [SELECT Id, Email FROM Contact];
Map<String, SObject> mapResult = CollectionUtils.stringMapFromCollectionByKey('', contacts);
System.assertEquals(0, mapResult.size(), 'Blank key should return empty map');
}
/**
* Tests stringMapFromCollectionByKey with an invalid key.
* Expects an empty map due to exception handling.
*/
@isTest
static void testStringMapFromCollectionByKey_InvalidKey() {
List<Contact> contacts = [SELECT Id, Email FROM Contact];
Map<String, SObject> mapResult = CollectionUtils.stringMapFromCollectionByKey('NonExistentField', contacts);
System.assertEquals(0, mapResult.size(), 'Invalid key should return empty map');
}
/**
* Tests idMapFromCollectionByKeyForList with AccountId as key.
* Verifies that each AccountId maps to a list containing the correct Contact.
*/
@isTest
static void testIdMapFromCollectionByKeyForList() {
List<Contact> contacts = [SELECT Id, AccountId FROM Contact WHERE AccountId != null];
Map<Id, List<SObject>> mapResult = CollectionUtils.idMapFromCollectionByKeyForList('AccountId', contacts);
System.assertEquals(3, mapResult.size(), 'Should map each AccountId to a list of Contacts');
for (Contact c : contacts) {
// Each AccountId should map to a list containing the correct Contact
System.assert(mapResult.get(c.AccountId).contains(c), 'List should contain the correct Contact');
}
}
/**
* Tests idMapFromCollectionByKeyForList with a nested field (Account.OwnerId).
* Verifies that each OwnerId maps to a list containing the correct Contact.
*/
@isTest
static void testIdMapFromCollectionByKeyForList_Nested1Level() {
List<Contact> contacts = [SELECT Id, AccountId, Account.OwnerId FROM Contact WHERE AccountId != null];
Map<Id, List<SObject>> mapResult = CollectionUtils.idMapFromCollectionByKeyForList('Account.OwnerId', contacts);
for (Contact c : contacts) {
Id ownerId = c.Account.OwnerId;
// Each OwnerId should map to a list containing the correct Contact
System.assert(mapResult.containsKey(ownerId), 'Map should contain OwnerId key');
System.assert(mapResult.get(ownerId).contains(c), 'List should contain the correct Contact');
}
}
/**
* Tests stringMapFromCollectionByKey with a nested field of more than 1 level.
* Should return empty map since only 1 level of nesting is supported.
*/
@isTest
static void testStringMapFromCollectionByKeyForList_NestedMoreThan1Level() {
// Use a valid Contact, but with a non-existent nested field (more than 1 level)
List<Contact> contacts = [SELECT Id, AccountId,Owner.Name FROM Contact WHERE AccountId != null LIMIT 1];
// Use a nested field where the parent is null (e.g., 'Account.Name' on a Contact with no AccountId)
Contact c = new Contact(FirstName='Edge', LastName='Case');
insert c;
List<Contact> edgeContacts = [SELECT Id, AccountId, Account.Name FROM Contact WHERE Id = :c.Id];
Map<String, SObject> mapResult = CollectionUtils.stringMapFromCollectionByKey('Account.Owner.Name', edgeContacts);
System.assertEquals(0, mapResult.size(), 'Should return null object when key is more than 1 level');
}
/**
* Tests idMapFromCollectionByKeyForList with an empty list.
* Expects an empty map as a result.
*/
@isTest
static void testIdMapFromCollectionByKeyForList_EmptyList() {
Map<Id, List<SObject>> mapResult = CollectionUtils.idMapFromCollectionByKeyForList('AccountId', new List<SObject>());
System.assertEquals(0, mapResult.size(), 'Empty input should return empty map');
}
/**
* Tests idMapFromCollectionByKeyForList with a null list.
* Expects an empty map as a result.
*/
@isTest
static void testIdMapFromCollectionByKeyForList_NullList() {
Map<Id, List<SObject>> mapResult = CollectionUtils.idMapFromCollectionByKeyForList('AccountId', null);
System.assertEquals(0, mapResult.size(), 'Null input should return empty map');
}
/**
* Tests idMapFromCollectionByKeyForList with a blank key.
* Expects an empty map as a result.
*/
@isTest
static void testIdMapFromCollectionByKeyForList_BlankKey() {
List<Contact> contacts = [SELECT Id, AccountId FROM Contact];
Map<Id, List<SObject>> mapResult = CollectionUtils.idMapFromCollectionByKeyForList('', contacts);
System.assertEquals(0, mapResult.size(), 'Blank key should return empty map');
}
/**
* Tests idMapFromCollectionByKeyForList with an invalid key.
* Expects an empty map due to exception handling.
*/
@isTest
static void testIdMapFromCollectionByKeyForList_InvalidKey() {
List<Contact> contacts = [SELECT Id, AccountId FROM Contact];
Map<Id, List<SObject>> mapResult = CollectionUtils.idMapFromCollectionByKeyForList('NonExistentField', contacts);
System.assertEquals(0, mapResult.size(), 'Invalid key should return empty map');
}
/**
* Tests stringMapFromCollectionByKeyForList with Email as key.
* Verifies that each Email maps to a list containing the correct Contact.
*/
@isTest
static void testStringMapFromCollectionByKeyForList() {
List<Contact> contacts = [SELECT Id, Email FROM Contact WHERE Email != null];
Map<String, List<SObject>> mapResult = CollectionUtils.stringMapFromCollectionByKeyForList('Email', contacts);
System.assertEquals(3, mapResult.size(), 'Should map each Email to a list of Contacts');
for (Contact c : contacts) {
// Each Email should map to a list containing the correct Contact
System.assert(mapResult.get(c.Email).contains(c), 'List should contain the correct Contact');
}
}
/**
* Tests stringMapFromCollectionByKeyForList with a nested field (Account.Name).
* Verifies that each Account Name maps to a list containing the correct Contact.
*/
@isTest
static void testStringMapFromCollectionByKeyForList_Nested() {
List<Contact> contacts = [SELECT Id, AccountId, Account.Name FROM Contact WHERE AccountId != null];
Map<String, List<SObject>> mapResult = CollectionUtils.stringMapFromCollectionByKeyForList('Account.Name', contacts);
for (Contact c : contacts) {
String accName = c.Account.Name;
// Each Account Name should map to a list containing the correct Contact
System.assert(mapResult.containsKey(accName), 'Map should contain Account Name key');
System.assert(mapResult.get(accName).contains(c), 'List should contain the correct Contact');
}
}
/**
* Tests stringMapFromCollectionByKeyForList with an empty list.
* Expects an empty map as a result.
*/
@isTest
static void testStringMapFromCollectionByKeyForList_EmptyList() {
Map<String, List<SObject>> mapResult = CollectionUtils.stringMapFromCollectionByKeyForList('Email', new List<SObject>());
System.assertEquals(0, mapResult.size(), 'Empty input should return empty map');
}
/**
* Tests stringMapFromCollectionByKeyForList with a null list.
* Expects an empty map as a result.
*/
@isTest
static void testStringMapFromCollectionByKeyForList_NullList() {
Map<String, List<SObject>> mapResult = CollectionUtils.stringMapFromCollectionByKeyForList('Email', null);
System.assertEquals(0, mapResult.size(), 'Null input should return empty map');
}
/**
* Tests stringMapFromCollectionByKeyForList with a blank key.
* Expects an empty map as a result.
*/
@isTest
static void testStringMapFromCollectionByKeyForList_BlankKey() {
List<Contact> contacts = [SELECT Id, Email FROM Contact];
Map<String, List<SObject>> mapResult = CollectionUtils.stringMapFromCollectionByKeyForList('', contacts);
System.assertEquals(0, mapResult.size(), 'Blank key should return empty map');
}
/**
* Tests stringMapFromCollectionByKeyForList with an invalid key.
* Expects an empty map due to exception handling.
*/
@isTest
static void testStringMapFromCollectionByKeyForList_InvalidKey() {
List<Contact> contacts = [SELECT Id, Email FROM Contact];
Map<String, List<SObject>> mapResult = CollectionUtils.stringMapFromCollectionByKeyForList('NonExistentField', contacts);
System.assertEquals(0, mapResult.size(), 'Invalid key should return empty map');
}
/**
* Tests getSobjectTypeFromList for the scenario where the list is empty.
* Should return 'sObject' as the type.
*/
@isTest
static void testGetSobjectTypeFromListReturnsSobject() {
// List is empty, so getSobjectTypeFromList should return 'sObject'
List<SObject> nullList = new List<SObject>();
String result = CollectionUtils.getSobjectTypeFromList(nullList);
System.assertEquals('sObject', result, 'List with null element should return sObject');
}
}
Core Features
  • Generic and Reusable: Write once, use everywhere.
  • Type-Safe: Maintain type integrity in your maps.
  • Dynamic: Handle nested keys effortlessly (e.g., Account.OwnerId).
Sample Scenarios Examples :

Let’s see how CollectionUtils can be used in different scenarios:

idMapFromCollectionByKey

Example 1: Grouping Contacts by Id:

Contact[] contacts = [SELECT Id, FirstName, LastName FROM Contact];
Map<Id, Contact> contactsByAccountId = (Map<Id, Contact>) 
    CollectionUtils.idMapFromCollectionByKey('Id', contacts);
stringMapFromCollectionByKey

Example 1: Grouping Contacts by Email:

Contact[] contacts = [SELECT Email, FirstName, LastName FROM Contact];
Map<String, Contact> contactsByEmail = ((Map<String, Contact>)  
    CollectionUtils.stringMapFromCollectionByKey('Email', contacts);

Example 2: Grouping Leads by Email

Lead[] leads = [SELECT Email, Name FROM Lead];
Map<String, Lead> leadsByEmail = (Map<String, Lead>) 
    CollectionUtils.stringMapFromCollectionByKey('Email', leads);
idMapFromCollectionByKeyForList

Example 1: Grouping Contacts into lists by Account Id:

Contact[] contacts = [SELECT AccountId, FirstName, LastName FROM Contact];
Map<Id, List<Contact>> groupedContacts = ((Map<Id, List<Contact>>)  
    CollectionUtils.idMapFromCollectionByKeyForList('AccountId', contacts);

Example 2: Grouping Contacts into lists by Account.Owner Id

Contact[] contacts = [SELECT Account.OwnerId, FirstName, LastName FROM Contact];
Map<Id, List<Contact>> groupedContactsAccountOwnerId = (Map<Id, List<Contact>>)  
    CollectionUtils.idMapFromCollectionByKeyForList('Account.OwnerId', contacts);
stringMapFromCollectionByKeyForList

Example 1: Grouping Opportunities into lists by Stage Name

Opportunity[] opportunities = [SELECT StageName, Amount FROM Opportunity];
Map<String, List<Opportunity>> opportunitiesByStage = (Map<String, List<Opportunity>>) 
    CollectionUtils.stringMapFromCollectionByKeyForList('StageName', opportunities);
Advantages of CollectionUtils
  • No More Repetitive Loops: Skip the boilerplate code and focus on business logic.
  • Handle Complex Keys: Easily work with nested relationships like Account.OwnerId.
  • Readable Code: Makes your codebase cleaner and easier to understand.
Summary Table
Method NameKey TypeValue TypeGrouped?Nested Key Support
idMapFromCollectionByKeyIdSObjectNoYes (1 level)
stringMapFromCollectionByKeyStringSObjectNoYes (1 level)
idMapFromCollectionByKeyForListIdList<SObject>YesYes (1 level)
stringMapFromCollectionByKeyForListStringList<SObject>YesYes (1 level)
Conclusion

The CollectionUtils class is a must-have in any Salesforce developer’s toolkit. By abstracting away repetitive for-loops, it streamlines data transformations, making your Apex code cleaner and more efficient. Whether you’re a seasoned developer or just getting started, this utility class will save you time and effort.

💡 Tip: Always validate your keys for uniqueness or nullability when using these methods to avoid runtime issues.

Source Reference – https://github.com/trailheadapps/apex-recipes

For more helpful articles please visit – https://thesalesforcedev.in/

One response to “Simplifying Data Transformation to Maps with CollectionUtils Class in Salesforce”

  1. Great insights, thanks for sharing!

    Like

Leave a reply to sprintpark Cancel reply