2

Example:

Opportunity Opp = [SELECT Name, Status__c, Zone__c, Invoice__r.Amount__c, Invoice__r.Date__c FROM Opportunity WHERE ID ='006a12nlfoenlew'];

I need to iterate over each field and see if the values are not null. If I want to check only Opportunity fields, I would do this:

Set<String> strFields = new Set<String>{'Name','Status__c','Zone__c'};

for(String s: strFields ){

//This prints the value of each field(included in the set) for the current Opportunity.
System.Debug(logginglevel.error,'*****'+Opportunity.get(s));

}

But, I need to get the values in lookup objects also. If I repeat the same,

Set<String> strFields = new Set<String>{'Invoice__r.Amount__c', 'Invoice__r.Date__c'};

for(String s: strFields ){

//This would throw "System.SObjectException: Invalid field Invoice__r.Amount__c for Opportunity" error.

System.Debug(logginglevel.error,'*****'+Opportunity.get(s));

}

Update: Even though the example I've give is very simple, In the code I'm working on, I've around 40 fields across 5 different look ups, ranging from 3-5 level deep relationships (Ex: Invoice__r.Task__r.Account.Contact.FirstName). I'm querying Opportunity(the base object) and I need to make sure the FirstName on contact is not null.

Is there a way I can iterate over, and get value in each field including lookup fields that are being queried?

3 Answers 3

0

You cannot call cross-object get. Take a look at the FieldReference class. It's open source, so you can just copy it into your org. Then you could do:

for (String invoiceField : invoiceFields)
    system.debug(FieldReference.build(invoiceField).getFrom(opportunity);

What you want probably cannot be accomplished in a clean way without recursive logic. If you want to do so without a helper class, you could do something along the lines of:

public with sharing class CrossObject
{
    public static Object get(SObject record, String field)
    {
        if (record == null) return null;
        if (!field.contains('.')) return record.get(field);
        record = record.getSObject(field.substringBefore('.'));
        field = field.substringAfter('.');
        return get(record, field);
    }
}
6
  • Adrian, even though the example I've give is very simple, In the code I'm working on, I've around 40 fields across 5 different look ups, ranging from 3-5 level deep relationships (Ex: Invoice__r.Task__r.Account.Contact.FirstName). I'm querying Opportunity(the base object) and I need to make sure the FirstName on contact is not null.
    – Avinash
    Commented Feb 16, 2016 at 2:43
  • @AvinashN You've clearly just copy-pasted this comment from the other answer. I don't see how it is relevant, the included code is perfectly flexible enough to suit your needs.
    – Adrian Larson
    Commented Feb 16, 2016 at 2:45
  • Adrian, Sorry about that. I thought you posted the answer for one level relationship and I felt I didn't give enough information. So I updated the question and added comments for both the answers with my exact scenario. I'm going through the FieldReference class. I'll let you know once I use it for my scenario. Thanks.
    – Avinash
    Commented Feb 16, 2016 at 2:52
  • @AvinashN It is a lot to grep at once, but using it is pretty simple. You just pass it a string, and once you have it constructed, call getFrom. If you want to write your own version though, then you would need to understand it before knowing what you might do differently.
    – Adrian Larson
    Commented Feb 16, 2016 at 2:54
  • FieldReference class worked without even single change. Thank you very much.
    – Avinash
    Commented Feb 16, 2016 at 17:08
1

Try this:

Set<String> invoiceStrFields= new Set<String>{'Amount__c', 'Date__c'};
Invoice__c invoice = Opportunity.Invoice__r;


for(String s: invoiceStrFields){
    System.Debug(logginglevel.error,'*****'+invoice.get(s)); 
}

You should get the Lookup object then you can use the getter with String fields

2
  • Karthik, Adrian, even though the example I've give is very simple, In the code I'm working on, I've around 40 fields across 5 different look ups, ranging from 3-5 level deep relationships (Ex: Invoice__r.Task__r.Account.Contact.FirstName). I'm querying Opportunity(the base object) and I need to make sure the FirstName on contact is not null.
    – Avinash
    Commented Feb 16, 2016 at 2:43
  • I feel its much cleaner and you clearly know which field comes from where, instead of bunch of Strings, and also Lookup doesn't guarantee to be present so its better to do level by level validation while moving lower Commented Feb 16, 2016 at 2:52
0

You need to use dynamic SOQL:

Dynamic SOQL refers to the creation of a SOQL string at runtime with Apex code. Dynamic SOQL enables you to create more flexible applications. For example, you can create a search based on input from an end user, or update records with varying field names.

and the SObject getSObject() method:

Returns the value for the specified field. This method is primarily used with dynamic DML to access values for external IDs.

In your case, the solution will look something like this:

Set<String> strFields = new Set<String>{'Name', 'AccountId', 'Account.Name', 'Account.CreatedDate'};

// First query for the Opportunity record
String queryStr = 'SELECT Id, StageName, CloseDate FROM Opportunity LIMIT 1';
Opportunity opp;

// Now add the referenced fields to the query
for (String field : strFields) {

    queryStr = queryStr.replaceAll(' FROM', ', ' + field + ' FROM');
}

// Now remove the trailing comma if needed
queryStr = queryStr.replaceAll(', FROM', ' FROM');

opp = database.query(queryStr);

for (String field : strFields) {

    String fieldValue;

    if (!field.contains('.')) {

        fieldValue = String.valueOf(opp.get(field));

    } else {

        String objectName = field.replaceAll('__r', '__c').split('\\.')[0];
        String fieldName = field.replaceAll('__r', '__c').split('\\.')[1];

        fieldValue = String.valueOf(opp.getSObject(objectName).get(fieldName));
    }
}

There are some additional checks you should do like ensure no field names are duplicated in the query string, ensure all field names in the set are valid etc. etc.

But that should get you almost there, just tailor it for your custom fields etc.

4
  • 1
    OP is about how to call get, not how to run the query.
    – Adrian Larson
    Commented Feb 16, 2016 at 15:27
  • Quite right, that's what I get for answering a question first thing in the morning. Updated my answer. Commented Feb 16, 2016 at 16:39
  • Fair enough. Your methodology still doesn't handle traversing multiple levels. Also it seems like you're doing more work than you need to with the replacement.
    – Adrian Larson
    Commented Feb 16, 2016 at 16:41
  • It should work for the OP, if you replace Account in my example with Invoice__c. It should also be possible to transverse multiple levels by using multiple getSObject() calls together. Commented Feb 16, 2016 at 16:46

You must log in to answer this question.

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