Utility Methods flexible enough for all sObject Types

QUESTION

I am trying to create a few utility methods that can work with any sObject that gets passed to it and return dynamic data-types. Unfortunately I’m starting to wonder, is this even possible?

EXAMPLE

Here is an example method I want for getting a list of sObject.Id from a Map<Id,List<sObject>> parameter.

public class Utils {

    public static List<object> getFieldListFromMap (Map<Id,List<sObject>> object_map, String field){
        List<object> objects = new List<object>();
        for(Id id : object_map.keyset()){                //traverse through map
            for (SObject obj : object_map.get(id) ) {    //traverse through List of sObects
                if(obj.get(field) != null)               //exclude null values
                    objects.add(obj.get(field));
            }
        }
        return objects;  
       // ^^ can I pass a string of the data-type and dynamically cast it?
    }

}

The hope from this is I can pass something like:

Map<Id,List<CampaignMember>> members; // a map collection of CampaignMembers by ContactID
List<Id> campaign_ids = Utils.getFieldListFromMap(members, 'CampaignId');

OR

Map<Id,List<Quote>> quotes; // a map collection of quotes my ContactId
List<Currency> opportunities = Utils.getFieldListFromMap(quotes, 'Custom_Total__c');

If I cannot describe the field data-type by the <string> parameter I’m passing here, then perhaps I can pass another <String> parameter declaring the expected data-type that should return in the List.

Any thoughts on this? Is it even possible? If not, what can be done, perhaps just dynamic sObjects but not fields?

Thanks in advance for any help I can get with this

EDIT: WORKING EXAMPLE

public static List<sObject> getListFromMap (Map<Id,List<sObject>> object_map, String obj_type){
     List<sObject> objects = new List<sObject>();
     Schema.SObjectType object_schema = Schema.getGlobalDescribe().get(obj_type);

     for(Id id : object_map.keyset()){
          for (SObject obj : object_map.get(id) ) {
               objects.add(object_schema.newSObject(obj.Id));
               // This gets the object type based on the objects Id
          }
     }
     return objects;
}

List<CampaignMember> members = Utils.getListFromMap(campaign_members, 'CampaignMember');
List<Quote> quote_list= Utils.getListFromMap(quotes, 'Quote');

This method currently gets my back a List of the object type I want back rather then just a generic sObect type. Now I just need to be able to do this with fields as well.

Answer

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);

Attribution
Source : Link , Question Author : Xtremefaith , Answer Author : sfdcfox

Leave a Comment