Utility Methods flexible enough for all sObject Types

It is absolutely possible! We have even built this exact utility where I work. However, I can't just give you the answer, but I will give some hints.

  • SObject methods: get will return the value of the field you are looking for
  • The aforementioned get method can take either a String or a Schema.SObjectField to specify the field you want to pluck. If you maintain that API your utility will be much nicer to use.
  • You probably want to return a Set of unique values and cast the type, which means one method (or collection thereof) per return type.
  • You may want to ignore nulls, especially if you want to use this utility in line in your SOQL.
  • If you want this utility to be able to traverse cross object relationships, it may be worth writing some sort of top level Field Reference class that does that sort of work for you. This will reap rewards in other utilities as well.

Here is a little bit of sample code to get you started.

public Set<Decimal> decimals(String field, List<SObject> records)
{
    Set<Decimal> results = new Set<Decimal>();
    for (SObject record : records)
    {
        Decimal result = Decimal.valueOf(record.get(field));
        if (result != null) results.add(result);
    }
    return results;
}
public Set<Decimal> decimals(String field, Map<Id, SObject> records)
{
    return decimals(field, records.values());
}
public Set<Decimal> decimals(Schema.SObjectField field, List<SObject> records)
{
    // Fill in the blanks!
}
public Set<Decimal> decimals(Schema.SObjectField field, Map<Id, SObject> records)
{
    // Fill in the blanks!
}

No more hints until you post more code!


You don't need to have a return type to get data back to the callee, and sometimes this is more convenient. Here's my version of a "get values from records" implementation:

public static void getValuesFromRecords(Object[] result, SObject[] source, SObjectField field) {
    for(SObject record: source) {
        result.add(record.get(field));
    }
}

Typical usage:

Decimal[] totalPrices = new Decimal[0];
Utils.getValuesFromRecords(totalPrices, Trigger.new, Opportunity.Amount);

The result is that totalPrices is loaded with the values from the records. Best of all, no casts are ever required, and everything "just works."

This works because objects are passed by reference, as they are in Java, so by adding the values directly to the callee's array, type-casting is transparent, and largely automatic (but beware, using the wrong data type can result in errors, of course).

Of course, in my particular utility class, there were different permutations, such as assigning a single value to many fields, or assigning different values to many fields (via Map<SobjectField, Object>). Feel free to explore.

This particular pattern is common in languages like C and C++, but apparently rare in Java (because of the concept of encapsulation), and therefore also in Apex Code. However, this pattern is often the only way to achieve maximum code efficiency, and it would be worth a developer's time to learn this technique for times this might be necessary.


Edit: Based on some comments, I've provided a more utilitarian function that returns a value that you can use immediately:

public static Object[] getValuesFromField(Type listType, SObject[] records, SObjectField field) {
    Object[] results = (Object[])listType.newInstance();
    for(SObject record: records) {
        results.add(record.get(field));
    }
    return results;
}

This new version allows a developer to specify what the return type should be. This may cause additional casting to be required, but doesn't require an already existing array to be used. Here's the new version in use:

Decimal[] totalPrices = (Decimal[])Utils.getValuesFromField(Decimal[].class, Trigger.new, Opportunity.Amount);