10

Essentially what I'm looking for is the Apex equivalent of the javascript delete operator


The reason I want to do this is that I want to store data in the standard Contact.Email field, but I need to handle the possibility that the data coming in could cause a save error with a INVALID_EMAIL_ADDRESS status code. I want to be able to detect the error, and retry the upsert with the value provided for the email address being stored into a new custom text field instead. To do this, I need to clear the value in the object's Email field. Since this is an upsert operation, I don't want to set the value of the field to NULL since that will clear the value in the database, which I would want to leave intact if it is already present.

Basically, what I'm looking for is the easiest way to get from:

Contact:{LastName=LastName, Email=FirstName.LastName@@Company.com, Id=003p000000LO97zAAD, ...}

to:

Contact:{LastName=LastName, Email__c=FirstName.LastName@@Company.com, Id=003p000000LO97zAAD, ...}

and specifically not:

Contact:{LastName=LastName, Email=null, Email__c=FirstName.LastName@@Company.com, Id=003p000000LO97zAAD, ....}
4
  • You're going to need to do it in a couple steps. – Adrian Larson Nov 22 '17 at 15:46
  • 2
    This capability does not exist in apex today. One way or another you'll need to construct a new SObject instance to do this – ca_peterson Nov 23 '17 at 2:52
  • Hi Martin, thoughts on renaming this question to use the word "sobject" instead of "object"? When I read object I thought you meant needing to use metadata api to physical alter the structure of the object/table, not your apex variable. Or re-phrase as "object variable"? Thanks! – Doug Ayers Nov 23 '17 at 5:28
  • 1
    @DougAyers I was using the term object in the sense of an instance of a class, but specifying that the object inherits from sObject does give you more to work with, and I was specifically concerned about the effects on DML operations, so I'd be willing to update the question. – martin Nov 23 '17 at 6:34
16

This can be done using combination of serialize and deserialize tricks with SObject and Map. In short, you serialize your original sobject into JSON then deserialize it into untyped Map then modify the map's keys to add/remove any fields you want - or don't want - set. Then serialize the map back into JSON then deserialize it into your typed SObject.

// start with your original sobject
Account acct1 = new Account(
    name = 'Turkey',
    type = 'Gobble'
);
System.debug( 'Step 1. Account object: ' + acct1 );

// Step 2. Get map of the sobject fields
// In this variant, the populated fields map is read-only so pass it into another map to get an editable copy
Map<String, Object> acctMap1 = new Map<String, Object>( acct1.getPopulatedFieldsAsMap() );
System.debug( 'Step 2. Account populated fields map: ' + acctMap1 );

// Another way to do Step 2 is good ol' serialize/deserialize trick
Map<String, Object> acctMap2 = (Map<String, Object>) JSON.deserializeUntyped( JSON.serialize( acct1 ) );
System.debug( 'Another way to do Step 2. Account serialized as json then deserialized back into untyped map: ' + acctMap2 );

// Step 3. Remove any fields you don't want
acctMap1.remove( 'Type' );
System.debug( 'Step 3. Remove undesired field "Type" from map: ' + acctMap1 );

// Step 4. Convert the map into sobject using the good ol' serialize/deserialize trick
Account acct2 = (Account) JSON.deserialize( JSON.serialize( acctMap1 ), Account.class );
System.debug( 'Step 4. Account after serializing map of our desired fields into json then deserializing the json back into sobject: ' + acct2 );
1
  • 1
    How would one go about doing this kind of thing with several layers of nested JSON? – Genko Dec 12 '18 at 17:51
0

I'd recommend you try to upsert your records via the Database.upsert method.

Database.UpsertResult resultList = Database.upsert(contactList, Contact.Id, false);

resultList will now contain your results.

// New list of contacts
List<Contact> contactList2 = new List<Contact>();

// Check each result for success
for(Integer i = 0; i < resultList.size(); i++)
{
    Database.UpsertResult result = resultList.get(i);

    if(!result.isSuccess())
    {
        for(Database.error singleError : result.getErrors())
        {
            if(singleError.getStatusCode() == Database.StatusCode.INVALID_EMAIL_ADDRESS)
            {
                Contact c = contactList.get(i);
                c.Email__c = c.Email;
                contactList2.add(c);
            }
        }
    }
}

Check out Database.UpsertResult and Database.Error.

Basically your allowing SF to upsert each row individually and then evaluate the results. You should - of course - handle other errors as well.

5
  • So far so good, the problem is with the next step though. How do you get rid of the Email field in c so you can re-upsert that object without erasing a value already stored in the database or re-encountering the same error? – martin Nov 22 '17 at 16:01
  • @martin I guess you can instantiate a new contact instead of using the one in the list: Contact c = new Contact(Id = contactList.get(i).Id); – Novarg Nov 23 '17 at 8:48
  • @martin Or re-query the records that fail instead of adding them to contactList2. Then populate your custom email field and upsert. – Christoph Nov 23 '17 at 11:00
  • @Novarg I agree that this will probably require the construction of a new object instance. – martin Nov 23 '17 at 12:51
  • @Christoph These records are coming in through push notifications from an external system. In this case I do also have the ability to make a callout to re-query these records, but I wouldn't want to have to do it that way. – martin Nov 23 '17 at 12:52

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.