How to get the Runtime-Type of an Object dynamically (for Primitive Data Types and SObjects)

Thanks to the comment of @KeithC I was able to go on with my research and as a result, it seems not possible to get a primitive data-type dynamically as a string. There are concepts to come close to it by the usage of instance of or try/catch in brute-force-like manner.

Since the primitive types are just a few, this workaround with instanceof seems acceptable for all types excepts of collections.

For list, map and set actually I was not able to find a satisfactory solution. Brute-force is not a general option, because of infinite possibilities. String.valueOf() can give a clue but not very much: for set and map, it returns a serialization wrapped in {} and for list it's wrapped in ().

Thanks to the feedback of @sfdcfox to try instanceof List<object>, map<object, object>, and set<object>, we seem to be able to detect lists. Unfortunately it does not work for maps and sets. This are my results:

object o;
o = new map<string,object>{}; 
system.debug( o instanceof map<string,object> ); // ==> true
system.debug( o instanceof map<object,object> ); // ==> false
o = new list<string>{}; 
system.debug( o instanceof list<string> ); // ==> true
system.debug( o instanceof list<object> ); // ==> true : only here it works
o = new set<string>{}; 
system.debug( o instanceof set<string> ); // ==> true
system.debug( o instanceof set<object> ); // ==> false

Any better approaches for collections are welcome!

The documentation to this topic is rare, as @mast0r said.

This links were helpful for me coming to this conclusion:

  • Return Name of class from instance of that class
  • https://stackoverflow.com/questions/9547563/how-do-i-introspect-the-class-of-a-variable-in-apex

The closest I could figure out with the approaches that we have right now you see below. Note that as an unfortunate we still can't detect sets and maps and we can't distinguish between decimal and double. The rest feels usable.

public class xs {
  public static string getType(Object o) {
    if(o==null) return '';              // we can't say much about null with our current techniques
    if(o instanceof SObject)            return ((SObject)o).getSObjectType().getDescribe().getName()+''; 
    if(o instanceof Boolean)            return 'Boolean';
    if(o instanceof Id)                 return 'Id';
    if(o instanceof String)             return 'String';
    if(o instanceof Blob)               return 'Blob';
    if(o instanceof Date)               return 'Date';
    if(o instanceof Datetime)           return 'Datetime';
    if(o instanceof Time)               return 'Time';
    if(o instanceof String)             return 'String';
    if(o instanceof Integer)            return 'Integer';
    if(o instanceof Long)               return 'Long';
    if(o instanceof Decimal)            return 'Decimal';  // we can't distinguish between decimal and double
    if(o instanceof Double)             return 'Double';   // we can't distinguish between decimal and double
    if(o instanceof List<object>)       return 'List';
    return 'Object';                    // actually we can't detect maps and sets and maps
  }
}

Actually I'm not that kind of big test-writer, but this could be rewritten and used as test. I run it as Execute Anonymous to verify the results:

list<string>t00 = new list<string>{'test'};             system.debug('List<string> : '  + xs.getType(t00));
Account     t01 = new Account();                        system.debug('Account : '       + xs.getType(t01));
Boolean     t02 = true;                                 system.debug('Boolean : '       + xs.getType(t02));
Boolean     t03 = false;                                system.debug('Boolean : '       + xs.getType(t03));
String      t04 = 'sdfsdf';                             system.debug('String  : '       + xs.getType(t04));
Id          t05 = [select id from user limit 1][0].Id;  system.debug('Id : '            + xs.getType(t05));
Blob        t06 = Blob.valueOf('testsdf');              system.debug('Blob : '          + xs.getType(t06));
Datetime    t07 = Datetime.now();                       system.debug('Datetime : '      + xs.getType(t07));
Time        t08 = Time.newInstance(18, 30, 2, 20);      system.debug('Time : '          + xs.getType(t08));
Date        t09 = (Date) Date.today();                  system.debug('Date : '          + xs.getType(t09));
Integer     t10 = 7;                                    system.debug('Integer : '       + xs.getType(t10));
Decimal     t11 = 18.99;                                system.debug('Decimal : '       + xs.getType(t11));
Double      t12 = 77.99;                                system.debug('Double : '        + xs.getType(t12));
Long        t13 = 9;                                    system.debug('Long : '          + xs.getType(t13));

EDIT

Here's the code I was actually looking for that does precisely what you wanted:

private string returnType( Object whatTypeAmI )
{
   String name = 'undefined';

   If( whatTypeAmI instanceof Integer )
   {
       name = 'Integer';
   }
   else if( whatTypeAmI instanceof Double)
   {
      name = 'Double';
   }
   else if( whatTypeAmI instanceof String)
   {
      name = 'String';
   }
   else if( whatTypeAmI instanceof Blob)
   {
      name = 'Blob';
   }
   else if( whatTypeAmI instanceof Boolean)
   {
      name = 'Boolean';
   }
   else if( whatTypeAmI instanceof Date)
   {
      name = 'Date';
   }
   else if( whatTypeAmI instanceof Datetime)
   {
      name = 'DateTime';
   }
   else if( whatTypeAmI instanceof Decimal)
   {
      name = 'Decimal';
   }
   else if( whatTypeAmI instanceof ID)
   {
      name = 'Id';
   }
   else if( whatTypeAmI instanceof Long)
   {
      name = 'Long';
   }
   else if( whatTypeAmI instanceof Time)
   {
      name = 'Time';
   }

   return(name);
}

Here's some old code I have I believe you could utilize for your code block to call from a class that I believe would serve the purpose of what you're looking for; particularly by using the boolean methods of the original test class. Using those, you wouldn't even need a try-catch block, just us If statements. When you finally get a "true" that's returned, you're done!

Using this method would of course require that you have data that isn't null in the list[0] or set[0] location of the collection you want to test. Potentially there could be an issue with a value of 0. Is it an integer, a decimal or a string? For maps, I'm not certain how helpful this would be, but you're a creative guy. Hopefully this will help point you towards a creative solution that will work for you.

Because I would like to directly call my Converter's class methods without creating a new instance of the class, I have defined all the methods as "static".

The methods for this class are:

  • ToString(Integer)
  • ToString(Double)
  • ToString(Long)
  • ToString(Boolean)
  • ToString(Date)
  • ToString(Date,format) sample: zConvert.ToStrong(mydate,'MM-dd-yy')
  • ToString(Time)
  • ToString(Time,format) sample: zConvert.ToStrong(myTime,'hh:mm:ss')
  • ToString(Datetime)
  • ToString(Datetime,format)
  • ToString(Decimal)
  • ToString(Decimal, ScientificNotaion) ScientificNotaion is a Boolean value and if false is passed then the string will not have scientific notations.
  • FileSizeToString(Long) Returns values such as "5.5 KB", "8 MB", etc. Parameter passed is in bytes.
  • CurrencyToString(Decimal, CurrencyChar) CurrencyChar can be "$", "£", etc

Here's the actual class:

   public class zConvert{

    // The Initial Developer of the Original Code is Sam Arjmandi. Portions created by
    // the Initial Developer are Copyright (C) 2008 the Initial Developer. All Rights Reserved.  
    // This Code is provided "As Is" without warranty of any kind.  

    public static String ToString(integer Value) {     
        /* string representation if an Integer value */     
        return Value.format(); 
        } 

    public static String ToString(Double Value) {   
        /* string representation if a Double value */
        return Value.format(); 
        } 

    public static String ToString(Boolean Value) {
        /* string representation if a Boolean value */    
        if (Value)      return 'true';
        else  return 'false'; 
        } 

    public static String ToString(Long Value) {
        /* string representation if a Long value */   
        return Value.format(); 
        } 

    public static String ToString(Date Value) {    
        /* string representation if a Date value */    
        return Value.format(); 
        } 

    public static String ToString(Date Value,String format) {
        /* string representation if a Date value with formatting */   
        Datetime temp = Datetime.newInstance(Value.year(), Value.month(), Value.day());   
        return temp.format(format); 
        } 

    public static String ToString(Datetime Value) {
        /* string representation if a Datetime value */    
        return Value.format(); 
        } 

    public static String ToString(Datetime Value,String format) {    
        /* string representation if a Datetime value with formatting */
        return Value.format(format);
        } 

    public static String ToString(Time Value) {
        /* string representation if a Time value */   
        return String.valueOf(Value); 
        } 

    public static String ToString(Time Value, String format) {
        /* string representation if a Time value with formating */
        Datetime temp = Datetime.newInstance(1970, 1, 1, Value.hour(), Value.minute(), Value.second());   
         return temp.format(format); 
         } 

    public static String ToString(Decimal Value) {
         /* string representation if a Decimal value */   
         return Value.format(); 
         } 

    public static String ToString(Decimal Value, Boolean ScientificNotation) {
          /* string representation if a Decimal value with or without Scientific Notation */   
         if (ScientificNotation)    return Value.format();
         else    return Value.toPlainString(); 
         } 

    public static String FileSizeToString(Long Value) {

       /* string representation if a file's size, such as 2 KB, 4.1 MB, etc */
       if (Value < 1024) return ToString(Value) + ' Bytes';
         else if (Value >= 1024 && Value < (1024*1024))    {
         //KB      
         Decimal kb = Decimal.valueOf(Value);      
         kb = kb.divide(1024,2);
         return ToString(kb) + ' KB';    
         }    else    if (Value >= (1024*1024) && Value < (1024*1024*1024))    {      
         //MB      
         Decimal mb = Decimal.valueOf(Value);      
         mb = mb.divide((1024*1024),2);      
         return ToString(mb) + ' MB';    
         }    else    {      
         //GB      
         Decimal gb = Decimal.valueOf(Value);      
         gb = gb.divide((1024*1024*1024),2);          
         return ToString(gb) + ' GB';    
         }   
         } 

    public static String CurrencyToString(Decimal Value, String CurrencyChar) {    
         return CurrencyChar + ToString(Value); 
         }
    }

Here's how to get 100% test coverage:

/* test string utils */
system.assertEquals('true',zConvert.ToString(true));
system.assertEquals('false',zConvert.ToString(false));
system.assertEquals('4/17/1960',zConvert.ToString(date.newInstance(1960, 4, 17)));
system.assertEquals('Apr, 26 04 11:24:40',zConvert.ToString(datetime.newInstance(2004, 4, 26, 23, 24, 40), 'MMM, dd yy hh:mm:ss'));
system.assertEquals('Apr, 17 1960',zConvert.ToString(date.newInstance(1960, 4, 17), 'MMM, dd yyyy'));
system.assertEquals('4/26/2004 11:24 PM',zConvert.ToString(datetime.newInstance(2004, 4, 26, 23, 24, 40)));
system.assertEquals('12.457',zConvert.ToString(decimal.valueOf('12.4567')));
system.assertEquals('0',zConvert.ToString(decimal.valueOf('.000000000000000000000012'), true));
system.assertEquals('12.4567',zConvert.ToString(decimal.valueOf('12.4567'), false));
system.assertEquals('3.142',zConvert.ToString(double.valueOf('3.14159')));
system.assertEquals('123,456',zConvert.ToString(123456));
system.assertEquals('1,234,567,890',zConvert.ToString(long.valueOf('1234567890')));
system.assertEquals('18:30:02.020Z',zConvert.ToString(time.newInstance(18, 30, 2, 20)));
system.assertEquals('06-30-02-302',zConvert.ToString(time.newInstance(18, 30, 2, 20), 'hh-mm-ss-ms'));
system.assertEquals('$123,456.17',zConvert.CurrencyToString(decimal.valueOf('123456.17'), '$'));
system.assertEquals('1,023 Bytes',zConvert.FileSizeToString(long.valueOf('1023')));
system.assertEquals('1,015.62 KB',zConvert.FileSizeToString(long.valueOf('1040000')));
system.assertEquals('1,020.43 MB',zConvert.FileSizeToString(long.valueOf('1070000000')));
system.assertEquals('1,015.14 GB',zConvert.FileSizeToString(long.valueOf('1090000000000')));