Can I tell which fields are present in an sObject variable?
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.
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