0

I have created an Apex Class to compare the current and prior values of SObjects. If differences are found the field labels are outputted to a ; separated string which I am using to populate a Multi-select Picklist.

This is working fine when run against real data however is failing in Test with the error "SObject row was retrieved via SOQL without querying the requested field" This is because the test SObjects I am inserting for newObject & priorObject contain the bare minimum of fields and there are a lot if fields on this Object.

Is there a way I can adjust the Class so it elegantly skips the missing fields? Could I base the fieldMap on the fields in the newObject or priorObject rather than the Object definition?

public with sharing class CompareRecords {
    
    public static String CompareRecords(String objectName, SObject newObject, SObject priorObject) {
        
        SObjectType objToken = Schema.getGlobalDescribe().get(objectName);
        DescribeSObjectResult objDef = objToken.getDescribe();
        Map<String, SObjectField> fieldMap = objDef.fields.getMap();
            System.Debug('fieldMap = '+ fieldMap);

        List<String> fieldsList = new List<String>();

        for (String field : fieldMap.keySet()){
            if(
                (fieldMap.get(field).getDescribe().isUpdateable() 
                || fieldMap.get(field).getDescribe().isCreateable()
                )
            ){
                if(
                   newObject.get(field) != (priorObject.get(field))
                ){
                    fieldsList.add(
                        fieldMap.get(field).getDescribe().getLabel()
                    );
                }
            }
        }

    String fieldsUpdated = string.join(fieldsList,';');
    return fieldsUpdated;
    }
}

2 Answers 2

1

Use getPopulatedFieldsAsMap(), and you'll have just the fields you're concerned about. Since this method can also return relationships, we'll filter those out.

public static String CompareRecords(SObject newObject, SObject priorObject) {
    Map<String, Object> oldValues = priorObject.getPopulatedFieldsAsMap();
    Map<String, Object> newValues = newObject.getPopulatedFieldsAsMap();
    Set<String> allFields = new Set<String>();
    Map<String, sObjectField> validFields = newObject.getSObjectType().getDescribe().fields.getMap();
    allFields.addAll(oldValues.keySet());
    allFields.addAll(newValues.keySet());
    String[] results = new String[0];
    for(String field: allFields) {
        sObjectField fieldToken = validFields.get(field);
        if(fieldToken != null && newValues.get(field) != oldValues.get(field)) {
            results.add(fieldToken.getDescribe().getLabel());
        }
    }
    return String.join(results, ';');
}

Edit: Accounted for case-sensitivity and to use field labels.

2
  • Thanks for this. The only issue is when the field values are added to the validFields Set the they are transformed to lover case. Therefore when it tries to match the field in validFields.contains(field) it fails because the fields are title Case. What would be the best way to adjust for this? Also in the results how can I return the FIeld Label? Commented Jun 2, 2023 at 21:37
  • @JeffCallahan I'd forgotten about that, actually. I edited the code to use the field map directly, which, even though it appears like other maps, is actually case-insensitive to the keys; we then use the field token to get the label.
    – sfdcfox
    Commented Jun 5, 2023 at 5:33
0

With a major assist from @sfdcfox it was able to figure it out...

public with sharing class CompareRecords {
    
    public static String CompareRecords(SObject newObject, SObject priorObject) {
    
        Map<String, Object> priorObjectMap = priorObject.getPopulatedFieldsAsMap();
        Map<String, Object> newObjectMap = newObject.getPopulatedFieldsAsMap();
    
        Set<String> allFields = new Set<String>();
        allFields.addAll(priorObjectMap.keySet());
        allFields.addAll(newObjectMap.keySet());

        Map<String, sObjectField> validFields = newObject.getSObjectType().getDescribe().fields.getMap();

        String[] fieldsUpdated = new String[0];
        for(String field: allFields) {
            sObjectField fieldToken = validFields.get(field);
            if(
                fieldToken != null && newObjectMap.get(field) != priorObjectMap.get(field)
                && (fieldToken.getDescribe().isUpdateable() || fieldToken.getDescribe().isCreateable())
            ) {
                fieldsUpdated.add(fieldToken.getDescribe().getLabel());
            }
        }
        System.debug('fieldsUpdated = ' + fieldsUpdated);
    return String.join(fieldsUpdated, ';');
    }
}

You must log in to answer this question.

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