17

I have some dynamic SOQL code in which I'd like to identify whether a method argument is a collection or a single value:

SObject[] query(SObjectType sobType, String field, Object data) {
    // Want to know if data is a collection e.g. set or list
    // that requires an "in" or a single value that requires
    // an "=" in the query. (May also need to use separate bind variables.)
}

I note that the sort of check shown immediately below seems to require an exact match for the type of the collection (Id in this case to return true) which is not what I want:

Object o = new Set<Id>();
System.debug(o instanceof Set<Id>);

Is there a simple way to make this determination?

(Yes there are various other ways to code this; checking first to see if there is a direct solution.)

PS

Looks like great caution is needed when using instanceof with collections.

Keith C
  • 135,775
  • 26
  • 201
  • 437

4 Answers4

18

If you are using a data binding, you do not need to know. = will work in either case, so you don't need to ever switch to IN for the use case you outline above. I used the following code in Execute Anonymous to demonstrate:

Object searchTerm = new Set<String> { 'test', 'test2' };
SObjectField field = Account.Name;
SObjectType sObjectType = Account.sObjectType;

system.debug(Database.query(
    'SELECT Id FROM ' + sObjectType + ' WHERE ' + field + ' = :searchTerm'
));

I also used searchTerm = 'test'; and the query ran just fine in either case.

Adrian Larson
  • 149,971
  • 38
  • 239
  • 420
  • 4
    Perfect - just what I need. Requiring "in" was a bad assumption on my part. (For completeness I wonder if there is a good answer to the title of the question but I don't need it right now given your approach.) – Keith C Jul 08 '16 at 19:44
  • 2
    I too thought that IN was required for collections. I'd +1 again if I could. – Derek F Jul 08 '16 at 20:34
12

Another alternative might be this:

public static Boolean isCollection(Object input)
{
    Object test;
    try{
       test = (List<Object>)input;
       return true;
    }
    catch(System.TypeException ex){}
    return false;
}

This should work for any Object. Note that this does not work for Sets or Maps, which both return conversion errors regardless. You could also modify it to the following:

public static Boolean isCollection(Object input)
{
    Object test;
    try{
       test = (List<Object>)input;
       return true;
    }
    catch(System.TypeException ex)
    {
        String message = ex.getMessage();
        return message.contains('Set<') || message.contains('Map<');
    }
    return false;
}
IllusiveBrian
  • 4,288
  • 1
  • 14
  • 22
2

If you really want to determine whether an Object instance is a collection of primitives, it would be a somewhat inelegant || chain:

public static Boolean isCollection(Object input)
{
    return input instanceof Set<String> || input instanceof List<String> ||
         input instanceof Set<Id> || input instanceof List<Id> ||
         input instanceof Set<Date> || input instanceof List<Date> ||
         input instanceof Set<Datetime> || input instanceof List<Datetime> ||
         input instanceof Set<Decimal> || input instanceof List<Decimal> ||
         input instanceof Set<Integer> || input instanceof List<Integer> ||
         input instanceof Set<Double> || input instanceof List<Double> ||
         input instanceof Set<Long> || input instanceof List<Long>;
}

You could add a few more instanceof checks, but it's unclear how sensical they would be, for example Set<Boolean>, List<Boolean>, Set<SObject>, List<SObject>, etc.

Adrian Larson
  • 149,971
  • 38
  • 239
  • 420
  • 2
    Yep inelegant especially as in this context it might be a collection of one of dozens of SObject types not the limited set of primitives. – Keith C Jul 08 '16 at 19:55
  • How would you use a single SObject in a filter? I can't see how that's relevant here, but you could check List also. – Adrian Larson Jul 08 '16 at 19:59
  • The data argument might be e.g. a List<Account> (with field being 'AccountId') where the query is looking for all the child Contact objects. – Keith C Jul 08 '16 at 20:19
  • 3
    Mind you, if they simply fixed their instanceOf nonsense, we could actually just do return o instanceof set<object> || o instanceof list<object> || o instanceof map<object, object>; However, until they choose to fix that problem, we're kind of stuck. – sfdcfox Jul 09 '16 at 01:10
  • Same root cause for why @IllusiveBrian's answer did not work. – Adrian Larson Jul 09 '16 at 01:11
2

How about this:

Json.serialize(data).startsWith('[')

Object collection = new List<String>{};
Object singleton  = '[';

system.debug(logginglevel.info,json.serialize(collection) + '\n' + Json.serialize(singleton));

produces:

[]
"[" 
cropredy
  • 71,240
  • 8
  • 120
  • 270
  • 2
    Think this might be towards the last resort end of the spectrum, particularly if the collection is large so that the JSON creation is a lot of work... – Keith C Jul 08 '16 at 20:23
  • 1
    @KeithC yes, I agree -- more of a theoretical solution than robustly practical – cropredy Jul 08 '16 at 20:25