13

Is there a way to retrieve the list of fields in a specific instance of an sObject?
That is, not all the fields that are defined on that sObject, but those fields which are populated on a particular instance of it in code.

Explanation of the problem

I can create an instance of account as follows

Account myAcc = new Account(firstname = 'Doug', billingCity=null);

If I do a System.debug I can see that this is an account, with values for the two fields which I have specified.

System.debug(myAcc); // This outputs Account:{BillingCity=null, FirstName=Doug}

Now if I am working with that variable myAcc, how do I know that it has values for firstname and billingCity, but not for (say) personemail?

I can retrieve the value of a given field by name. This isn't good enough as I can't tell which fields are null because they were explicitly made so (in this example billingCity) or null because they are not specified.

System.debug(myAcc.get('firstname'));    // This outputs Doug
System.debug(myAcc.get('billingcity'));  // This outputs null, it has been set to null
System.debug(myAcc.get('personemail'));  // This also outputs null, but it is not being set

This is important because if I issue an update on myAcc it updates only the explicitly set fields. So in this case it an update would set the firstname to Doug, would blank out the billingcity, but would leave the personemail untouched.

This matters to me as I am trying to merge two or more of them in code before doing an update.

Looking for a solution

Given the get syntax that you can use on an sObject, I was hoping I could get some map or list like behaviour out of it, but I haven't had any luck.

// Can we get a list of the fields that are in the instance?
System.debug(myAcc.keyset());  // Method does not exist or incorrect signature: [SOBJECT:Account].keyset()
// Can we retrieve something by index rather than name, and iterate?
System.debug(myAcc.get(0));    // Method does not exist or incorrect signature: [SOBJECT:Account].get(Integer)

I could serialize it into JSON, and then parse through it but that's pretty horrible and heavyweight.

Is there a nice easy solution to finding which fields are present in an instance of an sObject?

  • You could use dynamic Apex, describe the object to get the fields, and then loop through those to see which are populated. You won't be able to tell explicit null from implicit null, though. – Mike Chale Aug 21 '13 at 12:00
  • Problem is that the source of the data is not Salesforce, I'm receiving it from another system and I'm batch processing account records which may be inserts (which is fine) or partial updates (which is my problem). I guess I might have to do some pre-processing on the incoming data but I had really hoped there was a way to access the field data that must be there in the sObject in order for it to work. – Doug B Aug 21 '13 at 12:40
  • By pre-processing, do you mean a staging table/object? Because that's where I was going with this. – pchittum Aug 21 '13 at 13:06
  • It's tricky because at the point that I'm working with them I have them (up to 200) retrieved from the table and in memory. I could achieve my result fairly simply by dropping the batch size to 1 but that is not very elegant! For String values I was thinking I could pre-populate a blank string for when I deliberately want to null a field, but I'm not convinced that this is a good line to be going down. – Doug B Aug 22 '13 at 8:30
12

You can use JSON.deserializeUntyped() to convert any JSON-serialized SObject into a Map<String,Object> containing as its keys fields actually present on that SObject. Using your example with Account:

Account myAcc = new Account(Name = 'Doug', BillingCity=null);

// Serialize our Sobject into JSON
String s = JSON.serialize(myAcc);

// Deserialize it back into a key/value map
Map<String,Object> obj = 
    (Map<String,Object>) JSON.deserializeUntyped(s);

// Build a set containing the fields present on our SObject
Set<String> fieldsPresent = obj.keyset().clone();
// Salesforce always adds an extra 'attributes' key to SObjects
// when you do JSON stuff, so let's get rid of that.
fieldsPresent.remove('attributes');

System.debug(fieldsPresent);

// Returns: {Name,BillingCity}

There's nothing "horrible" or "heavyweight" about serializing into JSON / deserializing temporarily to get this information. Won't hurt your heap-size at all (as long as you don't maintain references after you're through with your work), and it only takes a couple of script statements.

  • Awesome JSON serialiser work Zach very impressed! – Andrew Fawcett Aug 21 '13 at 18:37
  • Thanks for this, and as you say it does achieve the result in only a few lines of code. It still feels like there's an inefficiency behind the scenes in having to serialize something and then deserialize it just to get a list of the fields. I have a feeling that it may be the only way to do it though. I will leave this question open a couple more days just in case someone can come up with a direct way to access the field list. If not I will mark this answer the winner! Thanks. – Doug B Aug 22 '13 at 8:25
  • @zachelrath this was the correct solution. Now that Summer 16 has introduced a simpler way, I've marked the new answer as the correct answer instead. Just so that anyone coming to this problem now can be aware there is a different approach. Thanks again. – Doug B Jun 7 '16 at 10:37
10

Finally there is a better solution to this. In the Summer '16 release, Salesforce have exposed a new method getPopulatedFieldsAsMap()

This does exactly what you would expect it to, returns fieldname -> fieldvalue map for only those fields explicitly populated on this instance of the sObject. We no longer need to serialize and deserialize JSON

Example of usage here:

Account myAcc = new Account(firstname = 'Doug', billingCity=null);
Map<String, Object> fieldToValue = myAcc.getPopulatedFieldsAsMap();
for (String key : fieldToValue.keySet()) {
    System.debug(key + '->' + fieldToValue.get(key));
}

Gives the result

FirstName->Doug
BillingCity->null

Here is the relevant release note https://releasenotes.docs.salesforce.com/en-us/summer16/release-notes/rn_apex_sobject_getmap.htm

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.