Thursday, February 2, 2012

Detecting the sObject type of Id's from ambiguous fields

When dealing with the OwnerId field, it is important to check if it is a User or a Group sObject you are dealing with before you start using it or you'll get errors. The Task object also has a few ambiguous fields that match up multiple Objects like the WhoId and the WhatId. In triggers and in other logic you will sometimes need to detect what the object is to either exclude or include it in the next bit of code. We dont want to iterate through code and update records if we don't need to, it will bog down the system. 


I have seen some of the ways out there that are used, particularly the one where you check if the first 3 letters of the ID is '500' or '300' but i do not think this is an accurate way of detection. I want one that the system tells me for sure what object it is. Unfortunately its not as dynamic as i would like it to be due to limitations in APEX, but it is still a much better detection than the above. 


I first made a helper class called GlobalHelper_IsSobjectType and populated it with a few methods that look like the following. It takes in the Id and after a check to make sure that an Id was actually passed and is not null, it try's to create an Object of that Type. If it can it will not fall into the Catch and will return true, other wise it will error and return false, but it will not error in a way that will halt your program from finishing(thus why its in a try catch). 




I made one for Account, Contact, Lead, User, and Opportunity since those are the ones I typically have to detect. When it comes to the OwnerId field I can just use the one for User since if it returns false i know its a group. I also found that I needed to Overload the method so it would work with string as well as Id: 




Ideally I was hoping to make the above but make it more dynamic so you pass the object type you wanted to detect as well as the id for the detecting. That way you would only need one method instead of one for each Object, but I have yet to find a way to make it work. Now of course you need to test the code, and I prefer as close to 100% coverage as possible so:






Questions?  
Twitter: @SalesForceGirl  or Facebook


5 comments:

  1. It's a shame you can't do this more cleanly - I'm never a fan of using exception handling for flow control. There should really be a better way of doing this, using instanceOf or something similar.

    I'm not too sure why you feel so strongly about not using the metadata calls and getting the keyPrefix for a type - it achieves the same thing, works for all objects, and is the system telling you what the object type is.

    Nevertheless, I think you can take what you have above and make it generic, how about iff you were to do something like this:

    public static boolean isIdType(Id sInId, Schema.SObjectType objType){

    if(sInId == null)
    return false;

    try{
    objType.newSObject(sInId);
    } catch(Exception ex){
    return false;
    }

    return true;
    }

    ReplyDelete
    Replies
    1. haha you did it! i was trying to get that to work all morning before i posted, hopping that I could post the most dynamic way, but got frustrated and figured that if it was possible someone would comment! :-) I tested your method and it works perfectly :-)

      Delete
  2. Another approach is to utilize the first 3 characters of the ID value because that defines the object. This way, it's 100% dynamic and you never have to actually bind your code to an actual object (good for ISVs). It should match the prefix attribute in the object's describe info. Here's what I do...

    public static String getObjectNameFromId(String strId){

    try{
    Id theId = strId; //tests if this is a real Id and also converts it to 18 char
    } catch (Exception e){
    return null; // not a real id. Converting a non-Id String to an Id throws an Exception
    }

    return getKeyPrefixToObjectMap.get(strId.substring(0,3));

    }


    public static map getKeyPrefixToObjectMap(){
    Map theMap = new Map();

    for (Schema.SObjectType so : Schema.getGlobalDescribe().values()) {
    Schema.DescribeSObjectResult od = so.getDescribe();
    if (od.getKeyPrefix() != null){
    theMap.put(od.getKeyPrefix(), od.getName().toLowerCase());
    }
    }

    return theMap;
    }

    ReplyDelete
    Replies
    1. Blogger stripped out the map definition in the 2nd method. it is defined as a string, string.

      Delete
  3. Randi not sure if you're stil interested in this but here is a way you could code a more dynamic check:

    public class ExcludeType
    {
    public static boolean isType(SObject compare, String checkType){
    try{
    Schema.SObjectType targetType = Schema.getGlobalDescribe().get(checkType);
    if(targetType == null){
    return null;
    }else if( compare.getSObjectType() == targetType){
    return true;
    }else{
    return false;
    }
    }catch(Exception e){
    System.debug('***** Couldn\'t create the object *****');
    return false;
    }
    return null;
    }
    }

    This way you can just compare the actual object against the type you want to check for like this if( ExcludeType.isType(myCase, 'Case'){ skip } else{ use } The cool thing about this is you could test for any object you may want to use or exclude without writing separate code for each.

    ReplyDelete