0

I have an requirement where I need to get the field api name using field label. For example, If I have field called Spouse_Phone__c(label = Spouse Phone), In apex I want to fetch the api name for that field using label 'Spouse Phone'. I have created below method:

public static String getObjectFieldApiByLabel(String objectName, String fieldLabel) {
    Map<String, Schema.SObjectField> fieldsMap = Schema.getGlobalDescribe().get(objectName).getDescribe().fields.getMap();
    
    for (Schema.SObjectField field : fieldsMap.values()) {
        if (field.getDescribe().getLabel() == fieldLabel) {
            return field.getDescribe().getName();
        }
    }
    
    return null;
}

But I do not like this method as you can see I am passing single field label and comparing it with each field label and if it matches then returning field api name. If I have 3 fields with label I will call this method 3 times and for loop will run 3 times till it matches field labels. This is lot of processing time. I am struggling with how I can make this dynamic and more efficient. Any ideas or suggestion might be helpful.

1
  • If you are calling this once, then AFAIK not anything you can do. If you are calling it many times then you could cache the full set on the first call (using objectName.fieldLabel as the key).
    – Keith C
    Jun 5, 2023 at 12:47

1 Answer 1

1

The most helpful things to do here would probably be:

  • Cache (via a static variable) the results of Schema.getGlobalDescribe() so that you only call that method once per transaction (because getGlobalDescribe() is a slow operation)
  • Use a nested map (Map<String, Map<String, String>>) to cache field data for individual SObjects as you encounter them. The outer map's key would be the SObject name, the inner map's key would be the field label, and the inner map's value would be the api name

Honestly, caching the getGlobalDescribe() call is probably the most important thing here.

Because you're trying to work with field labels here, there isn't a way to avoid looping over the SObject fields. We can, however, limit that to looping just once per SObject. Doing this for every SObject the first time that any call is made would take a considerable amount of time, and probably wouldn't be worth it from a CPU limit perspective (and it could potentially also put undue stress on your heap limit).

By processing an SObject when it gets used (i.e. lazy loading), you know that you're only spending time on something when it is needed. With a single SObject being able to hold up to 800 fields (with managed packages able to add more, I believe), looping over those each time isn't the end of the world. You can build that map that I suggested, but I doubt you'd see much of a performance gain (in CPU limit and/or measured clock time) unless you're calling this method hundreds of times (or maybe not until you call it thousands of times per transaction).

As an example to get you on the right path

public class CachingSObjectFieldUtil {
    private static Map<String, Schema.DescribeSObjectResult> sobjDescribeBySObjectName;
    private static Map<String, Map<String, String>> sobjectNameAndFieldLabelToAPIName;

    // This is a static initialization block
    // It gets run the first time that this class is referenced in a transaction
    static {
        // cache the global describe so we don't need to call it again
        sobjDescribeBySObjectName = Schema.getGlobalDescribe();

        // Populating the outer map should be relatively quick, and save a little
        //   bit of trouble
        for(String sobjName :sobjDescribeBySObjectName.keySet()) {
            sobjectNameAndFieldLabelToAPIName.put(sobjName, new Map<String, String>());
        }
    }

    public static String getFieldAPINameFromLabel(String sobjName, String label) {
        Map<String, String> labelToAPIName = sobjectNameAndFieldLabelToAPIName.get(sobjName);

        if(labelToAPIName == null) {
            // bad SObject name, error out
        }

        if(labelToAPIName.isEmpty()) {
            // first time encountering this SObject
            // populate the labelToAPIName map by 
            // 1: fetching the cached describe info for the sobject
            // 2: looping over .fields.getMap().values()
            // 3: using getDescribe().getLabel() and getDescribe().getName()
        }

        return labelToAPIName.get(label);
    }
}

A few other notes:

  • Using maps here means that the SObject name and field label are going to be case-sensitive. Using String's .toLower() or .toUpper() may be a good idea
  • Expect issues if you have duplicate field labels (on a single SObject. Things like Description on Account and Opportunity are not an issue)
  • Non-primitive types (i.e. things that aren't integer, string, boolean, etc...) are generally stored in collections as references. So modifying labelToAPIName in my example means that we're automatically updating the data in sobjectNameAndFieldLabelToAPIName (i.e. you don't need to call .put() at all after putting into that map in the static initializer)

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .