Array Filter

Recently I needed a way to quickly filter out an array (i.e. list) of sObjects in Apex. Since I was a C# developer for 5 years, I immediately remembered how I used LINQ in C# for this purpose and I hoped there’s something similar in Apex as well. However, in Apex, there is no easy way to filter through a list without using the for loop.

I’m usually trying to limit the number of DML statements in Apex, so I’d often query more records at once (if I know I’m not going to reach 50.000 records limit) and then filter through it via for loop in case I need to get multiple sets of records from that query. Because of this need, I decided to create a helper class that will help me achieve this without writing the for loop every time I need to filter something. I’m sharing the class with you below, and you can share your thoughts with me in the comments.

public class ArrayFilter {

  public static List<sObject> filter(List<sObject> arrayToFilter, List<ArrayFilter.Filter> filters, String filterLogic){
    List<sObject> retVal = new List<sObject>();

    if(arrayToFilter == null){
      return retVal;
    }

    if(filterLogic == 'OR'){
      for(Integer i = 0; i < arrayToFilter.size(); i++){
        for(Integer j = 0; j < filters.size(); j++){
          Filter f = filters[j];
          if(ArrayFilter.Assert(arrayToFilter[i], f)){
            retVal.add(arrayToFilter[i]);
            continue;
          }
        }
      }
    }else if(filterLogic == 'AND'){
      for(Integer i = 0; i < arrayToFilter.size(); i++){
        Boolean passedTheFilter = true;
        for(Integer j = 0; j < filters.size(); j++){
          Filter f = filters[j];
          if(ArrayFilter.Assert(arrayToFilter[i], f) == false){
            passedTheFilter = false;
            break;
          }
        }
        if(passedTheFilter){
          retVal.add(arrayToFilter[i]);
        }
      }
    }else{
      throw new ArrayFilterException('Invalid filter logic "' + filterLogic + '" in class ArrayFilter. \n\nAvailable options are "OR" and "AND"');
    }

    return retVal;
  }

  public static Boolean Assert(sObject o, Filter f){
    if(f.valueType == 'Boolean'){
      if(f.operator == '!='){
        return ((Boolean)o.get(f.fieldName)) != f.valBoolean;
      }else{
        return ((Boolean)o.get(f.fieldName)) == f.valBoolean;
      }
    }else if(f.valueType == 'Date'){
      if(f.operator == '!='){
        return ((Date)o.get(f.fieldName)) != f.valDate;
      }else if(f.operator == '>'){
        return ((Date)o.get(f.fieldName)) > f.valDate;
      }else if(f.operator == '<'){
        return ((Date)o.get(f.fieldName)) < f.valDate;
      }else{
        return ((Date)o.get(f.fieldName)) == f.valDate;
      }
    }else if(f.valueType == 'DateTime'){
      if(f.operator == '!='){
        return ((DateTime)o.get(f.fieldName)) != f.valDateTime;
      }else if(f.operator == '>'){
        return ((DateTime)o.get(f.fieldName)) > f.valDateTime;
      }else if(f.operator == '<'){
        return ((DateTime)o.get(f.fieldName)) < f.valDateTime;
      }else{
        return ((DateTime)o.get(f.fieldName)) == f.valDateTime;
      }
    }else if(f.valueType == 'Time'){
      if(f.operator == '!='){
        return ((Time)o.get(f.fieldName)) != f.valTime;
      }else if(f.operator == '>'){
        return ((Time)o.get(f.fieldName)) > f.valTime;
      }else if(f.operator == '<'){
        return ((Time)o.get(f.fieldName)) < f.valTime;
      }else{
        return ((Time)o.get(f.fieldName)) == f.valTime;
      }
    }else if(f.valueType == 'Integer'){
      if(f.operator == '!='){
        return ((Integer)o.get(f.fieldName)) != f.valInteger;
      }else if(f.operator == '>'){
        return ((Integer)o.get(f.fieldName)) > f.valInteger;
      }else if(f.operator == '<'){
        return ((Integer)o.get(f.fieldName)) < f.valInteger;
      }else{
        return ((Integer)o.get(f.fieldName)) == f.valInteger;
      }
    }else if(f.valueType == 'Decimal'){
      if(f.operator == '!='){
        return ((Decimal)o.get(f.fieldName)) != f.valDecimal;
      }else if(f.operator == '>'){
        return ((Decimal)o.get(f.fieldName)) > f.valDecimal;
      }else if(f.operator == '<'){
        return ((Decimal)o.get(f.fieldName)) < f.valDecimal;
      }else{
        return ((Decimal)o.get(f.fieldName)) == f.valDecimal;
      }
    }else if(f.valueType == 'Double'){
      if(f.operator == '!='){
        return ((Double)o.get(f.fieldName)) != f.valDouble;
      }else if(f.operator == '>'){
        return ((Double)o.get(f.fieldName)) > f.valDouble;
      }else if(f.operator == '<'){
        return ((Double)o.get(f.fieldName)) < f.valDouble;
      }else{
        return ((Double)o.get(f.fieldName)) == f.valDouble;
      }
    }else if(f.valueType == 'Long'){
      if(f.operator == '!='){
        return ((Long)o.get(f.fieldName)) != f.valLong;
      }else if(f.operator == '>'){
        return ((Long)o.get(f.fieldName)) > f.valLong;
      }else if(f.operator == '<'){
        return ((Long)o.get(f.fieldName)) < f.valLong;
      }else{
        return ((Long)o.get(f.fieldName)) == f.valLong;
      }
    }else if(f.valueType == 'Id'){
      if(f.operator == '!='){
        return ((Id)o.get(f.fieldName)) != f.valId;
      }else{
        return ((Id)o.get(f.fieldName)) == f.valId;
      }
    }else if(f.valueType == 'String'){
      if(f.operator == '!='){
        return !((String)o.get(f.fieldName)).equals(f.valString);
      }else if(f.operator == 'CONTAINS'){
        return ((String)o.get(f.fieldName)).contains(f.valString);
      }else if(f.operator == 'STARTS'){
        return ((String)o.get(f.fieldName)).startsWith(f.valString);
      }else if(f.operator == 'ENDS'){
        return ((String)o.get(f.fieldName)).endsWith(f.valString);
      }else{
        return ((String)o.get(f.fieldName)).equals(f.valString);
      }
    }

    return false;
  }

  public class Filter {

    public String fieldName {get; private set;}
    public String valueType {get; private set;}
    public String operator {get; private set;}//>,<,=,!=, CONTAINS, STARTS, ENDS

    public Boolean valBoolean {get; private set;}
    public Date valDate {get; private set;}
    public DateTime valDateTime {get; private set;}
    public Time valTime {get; private set;}
    public Integer valInteger {get; private set;}
    public Decimal valDecimal {get; private set;}
    public Double valDouble {get; private set;}
    public Long valLong {get; private set;}
    public Id valId {get; private set;}
    public String valString {get; private set;}

    public Filter(String fieldName, String operator, Boolean value){
      this.fieldName = fieldName;
      this.valueType = 'Boolean';
      this.valBoolean = value;
      this.operator = operator;
    }

    public Filter(String fieldName, String operator, Date value){
      this.fieldName = fieldName;
      this.valueType = 'Date';
      this.valDate = value;
      this.operator = operator;
    }

    public Filter(String fieldName, String operator, DateTime value){
      this.fieldName = fieldName;
      this.valueType = 'DateTime';
      this.valDateTime = value;
      this.operator = operator;
    }

    public Filter(String fieldName, String operator, Time value){
      this.fieldName = fieldName;
      this.valueType = 'Time';
      this.valTime = value;
      this.operator = operator;
    }

    public Filter(String fieldName, String operator, Integer value){
      this.fieldName = fieldName;
      this.valueType = 'Integer';
      this.valInteger = value;
      this.operator = operator;
    }

    public Filter(String fieldName, String operator, Decimal value){
      this.fieldName = fieldName;
      this.valueType = 'Decimal';
      this.valDecimal = value;
      this.operator = operator;
    }

    public Filter(String fieldName, String operator, Double value){
      this.fieldName = fieldName;
      this.valueType = 'Double';
      this.valDouble = value;
      this.operator = operator;
    }

    public Filter(String fieldName, String operator, Long value){
      this.fieldName = fieldName;
      this.valueType = 'Long';
      this.valLong = value;
      this.operator = operator;
    }

    public Filter(String fieldName, String operator, Id value){
      this.fieldName = fieldName;
      this.valueType = 'Id';
      this.valId = value;
      this.operator = operator;
    }

    public Filter(String fieldName, String operator, String value){
      this.fieldName = fieldName;
      this.valueType = 'String';
      this.valString = value;
      this.operator = operator;
    }

  }
}

Sample usage

Below you can see some of the ways you could use this ArrayFilter class to filter a list of objects.

Example #1:

//filter by String
List<sObject> filteredArray = ArrayFilter.filter(someArrayToFilter, new ArrayFilter.Filter[]{new ArrayFilter.Filter('Name', '=', 'John Doe')}, 'OR');

Example #2:

List<ArrayFilter.Filter> filters = new List<ArrayFilter.Filter>();
filters.add(new ArrayFilter.Filter('Migration_Start_Date__c', '=', Date.newInstance(2000,1,2)));
filters.add(new ArrayFilter.Filter('Migration_Start_Date__c', '>', Date.newInstance(2010,3,4)));

List<Account> filtered = ArrayFilter.filter(someArrayToFilter,filters, 'OR');

Example #3:

//text supports operators: =, !=, CONTAINS, ENDS, STARTS
List<ArrayFilter.Filter> filters = new List<ArrayFilter.Filter>();
filters.add(new ArrayFilter.Filter('Phone', 'STARTS',  '0355'));
filters.add(new ArrayFilter.Filter('NAME', 'STARTS',  'Jo'));

List<Account> filtered = ArrayFilter.filter(someArrayToFilter,filters, 'AND');
Written on May 15, 2016