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.

0

4 Answers 4

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.

2
  • 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 8, 2016 at 19:44
  • 2
    I too thought that IN was required for collections. I'd +1 again if I could.
    – Derek F
    Jul 8, 2016 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;
}
6
  • pretty clever -- debug log gets littered with exceptions though
    – cropredy
    Jul 8, 2016 at 20:19
  • Sneaky - one to remember.
    – Keith C
    Jul 8, 2016 at 20:21
  • Using exceptions for flow control like this is generally a code smell, and should be avoided, but I still think this one is the best of the solutions to the letter of the question.
    – Derek F
    Jul 8, 2016 at 20:35
  • 2
    Are you sure it actually works with Set or Map when the type is concrete? I'm 90% certain that will fail.
    – Adrian Larson
    Jul 8, 2016 at 20:48
  • 2
    @IllusiveBrian I did a small collection of tests, and basically proved that map, set, and lists are fundamentally type-broken. See apex String instanceof Object?.
    – sfdcfox
    Jul 9, 2016 at 1:21
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.

5
  • 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 8, 2016 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<SObject> also.
    – Adrian Larson
    Jul 8, 2016 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 8, 2016 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 9, 2016 at 1:10
  • Same root cause for why @IllusiveBrian's answer did not work.
    – Adrian Larson
    Jul 9, 2016 at 1: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:

[]
"[" 
2
  • 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 8, 2016 at 20:23
  • 1
    @KeithC yes, I agree -- more of a theoretical solution than robustly practical
    – cropredy
    Jul 8, 2016 at 20:25

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .