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

Tags:

Schema

Apex