Take the 2-minute tour ×
Salesforce Stack Exchange is a question and answer site for Salesforce administrators, implementation experts, developers and anybody in-between. It's 100% free, no registration required.

Is it possible to get the Type of an Object (it's not an SObject)?

String s = 'test';
doSomething(s);

public static void doSomething(o Object) {
    // need to find out if o is a string or an integer
    type = IsThereAMethodToGetTheTypeOf(o) // ?
    if(type='String') {
        // do something for strings here ....
    } else {
        // do something else ....
    }
}

I think the Type class can't help me, because it has no way to get the type of an existing object instance, right? Any there any other way?

Since there is most likely no perfect solution possible within the current platform limitations, I posted an Idea on IdeaExchange here: https://success.salesforce.com/ideaView?id=08730000000l9wHAAQ

For SObject I have already this

public static string getType(SObject obj) {
    if(obj==null) return '';
    return obj.getSObjectType().getDescribe().getName()+'';
}
share|improve this question
    
One silly (but actually usable) way I found is to use a couple of try/catches to iterate over possible types by typecast and assignment like try { Integer testInt = (Integer) o; ... } catch(Exception e) { try { String testStr = (String) o; ... } } - but can we do this better? –  Uwe Heim Aug 28 at 11:32
    
I can't even find any sfdc doc about 'Object' class :( –  mast0r Aug 28 at 11:48
1  
There is instanceof which is better than the casting approach though that also requires hard coding of the types. And it used to return true for null but I think that may now be fixed. –  Keith C Aug 28 at 12:20

4 Answers 4

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:

share|improve this answer
    
Seems ripe for an idea on idea exchange Uwe! –  Peter Aug 28 at 14:06
1  
instanceof List<object>, map<object, object>, and set<object> should return true for the respective data types, regardless of the parameters provided. –  sfdcfox Aug 28 at 14:11
    
@Peter here it is: success.salesforce.com/ideaView?id=08730000000l9wHAAQ ;-) –  Uwe Heim Aug 28 at 16:01
    
@sfdcfox : I updated this answer above with your suggestion of instanceof List<object>, map<object, object>, and set<object>. Unfortunately it does only work for List<object> which is a really inconsistent behavior of the platform - at least from my perspective. I added my current best-possible solution (which still has flaws) here as xs.getType(Object o). Thanks to your hint, it can now at least detect Lists (as just "List", not what kind of List<Kind>). If someone figures out how to catch Map and Set or how to figure Collection<Kind>, it would be awesome! –  Uwe Heim Aug 29 at 7:19
up vote 1 down vote accepted

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));
share|improve this answer
    
It's quite obvious but, if you implement something like this, note that order of the lines above is important to get proper results returned. –  Uwe Heim Aug 28 at 15:28
    
Also instanceof still returns true for null as of today (2014-08-28) –  Uwe Heim Aug 28 at 15:31
    
You can detect sets and maps using overloading. And also distinguish Double from Decimal. Added an answer as the code's too long to fit in a comment. –  Dominic Aug 28 at 17:55

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')));
share|improve this answer
    
Thanks @crmprogdev, that's the way we have to go right now, I guess. Pretty much what I came up in the end. But also no full solution for set, map and list. Did you check, if your code differs between double and decimal? That I couldn't get either. I also put in the solution for SObject, so we have a single method for both use cases. –  Uwe Heim Aug 28 at 16:07
    
Yes there's an instance of Double in the WhatTypeAmI class. Its also accounted for in the zConvert code as well. Ditto for longs. It took me a while to find the code in my archives, thus the reason you didn't get the Edit portion first as that's what I was looking for initially. –  crmprogdev Aug 28 at 16:26
    
I've seen the "instanceof double" in your code, but actually I doubt that it can work. It probably returns always the result, which you try to detect first. But correct me, if I'm wrong. –  Uwe Heim Aug 28 at 16:31
    
I got the whatTypeAmI code from a very experienced and established developer in our SFDUG quite some time ago who was very helpful to me when I was getting started. I've not specifically tested it against a double. I can only say that I'd trust any code he's given me. I recommend you test it yourself to see if it reliably returns the results you expect. –  crmprogdev Aug 28 at 17:27
    
thx will do so. –  Uwe Heim Aug 28 at 17:29

The platform can distinguish between all these types, so if we accept that we're going to be coding a list and matching against it, we can do it this way. Let overloading sort it out instead of trying InstanceOf on an Object. This class distinguishes between Double and Decimal, and can detect any map or list I've thrown at it. I'd use statics IRL but this runs nicely in Execute Anonymous.

class TestType
{
    public string whatAmI(Double d)
    {
        return 'I am a Double';
    }

    public string whatAmI(Decimal d)
    {
        return 'I am a Decimal';
    }

    public string whatAmI(map<Id,list<Account>> d)
    {
        return 'I am a map of Ids to lists of Accounts';
    }
}

TestType tt = new TestType();
Decimal dec = 123.456;
Double dub = 789.987;
map<Id, list<Account>> prettySpecificMap = new map<Id, list<Account>>();

system.debug('A decimal? ' + tt.whatAmI(dec));
system.debug('A double? ' + tt.whatAmI(dub));
system.debug('Something very specific? ' + tt.whatAmI(prettySpecificMap));
share|improve this answer
    
Interesting approach! Due to the infinite possibilities it has the same flaws for maps, sets and list as the instanceof-approach - you can't write one single method in an utility class, which is reusable for each and every use case without modification. But to distinguish between Decimal and Double it would be usable in a general solution. –  Uwe Heim Aug 28 at 18:08
    
Thats awesome. You cant use Map<Object, Object> with this approach as a catch all? –  Phil Rymek Aug 28 at 18:08
    
Although it has some of the same flaws, it has some advantages :). I will try the map<object, object> thing, which won't be a catch-all, but will be a catch-a-lot... –  Dominic Aug 28 at 18:10
1  
whatAmI(map<object, object> d) doesn't seem viable. I get "method doesnt exist or incorrect signature" in what you'd think of as a catch-all case. Each specific map does have to be defined individually. Although map<Id,SObject> does work. And then you can use SObject describe to find out what it actually is. –  Dominic Aug 28 at 18:19
    
I tried this way but beside of the differentiation between Decimal and Double I found it less flexible. Having a general reflectional solution in mind, the biggest disadvantage for me is that you can't use it to test an instance of Object of being something more specific like String or Accounts dynamically during runtime. Say you have a mixed List<object> and want to process each element according to it's real type. So I would stick with xs.getType(Object o) defined in my answer and live with it's flaws until someone or Salesforce show us how to do it better. –  Uwe Heim Aug 29 at 7:07

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.