Calculate Mathematical Text Expression in Apex

 I recently came across the following issue. I created custom metadata, where I can set mathematical expression with combination of fixed values and merge fields. 

For example: (5+{!Total_Days__c})*10

Then, in apex code, I needed to calculate this expression based on the record values.

Replacing the merge fields with actual value was very easy, using String methods like substring/replace, but calculating the expression was less easy, as there is no build in method that handling it. 

When searching for similar solution in other language, most of the suggestion are based on Javascript method eval.

Eventually I took some time and wrote such service myself.

It might missing some error handling for wrong input, so when using it might want to wrap the function call with try-catch, but generally with correct input it does produce the correct number.


public class StringToNumber{

    public static Decimal convertStringToNum(String expression){

        Decimal val = 0;
        String copyExp=expression;

        while(copyExp.contains('(')){

            String innerLogic = copyExp.substring(copyExp.lastIndexOf('(') + 1).subStringBefore(')');
            
            Decimal innerCalc=getFormulaInnerValue(innerLogic);

            if(copyExp.contains('(' +  innerLogic + ')')){
                copyExp=copyExp.replace('(' +  innerLogic + ')', String.valueOf(innerCalc));
            }
            else{
                copyExp=copyExp.replace('(', '');
            }
        }

        return getFormulaInnerValue(copyExp);
    }

    private static Decimal getFormulaInnerValue(String innerExp){

        Decimal val = 0;
        String copyExp = innerExp;

        //Should not have indexes at this point only numbers and operators
        list<String> l_logicValues = copyExp.split('[-+//*]');


        //Get list of operators
        list<String> l_operators = new list<String>();

        for(Integer formulaIndex = 0; formulaIndex < innerExp.length(); formulaIndex ++){
            if(innerExp.charAt(formulaIndex) == 43
                || innerExp.charAt(formulaIndex) == 45
                || innerExp.charAt(formulaIndex) == 42
                || innerExp.charAt(formulaIndex) == 47){

                l_operators.add(innerExp.substring(formulaIndex, formulaIndex + 1));
            }
        }
        
        Integer nextIndex=0;
    
        //empty value indicate minus value
        if(String.isBlank(l_logicValues.get(0))){
            val = Decimal.valueOf(l_logicValues.get(1).trim()) * (-1);
            l_logicValues.remove(0);
            l_operators.remove(0);
        }
        else{
            val =Decimal.valueOf(l_logicValues.get(0).trim());
        }
            
            
        for(Integer valIndex=1; valIndex<l_logicValues.size(); valIndex++){
            String sign = l_operators.get(nextIndex);
            Decimal nextVal = 0;
            
            //empty value indicate minus value
            if(String.isBlank(l_logicValues.get(valIndex))){
                valIndex++;
                nextIndex++;
                nextVal = decimal.valueOf(l_logicValues.get(valIndex).trim()) * (-1);
            }
            else{
                nextVal = decimal.valueOf(l_logicValues.get(valIndex).trim());
            }
            
            
            val = addFixValue(val, nextVal, sign);
            nextIndex++;
        }
        return val;
    }

    private static Decimal addFixValue(
        Decimal currenctVal,
        decimal nextVal,
        String sign){

        Decimal newVal = currenctVal;
            
        if(sign == '+'){
            newVal = nextVal + currenctVal;
        }
        else if(sign == '-'){
            newVal = currenctVal - nextVal;
        }
        else if(sign == '*'){
            newVal = nextVal * currenctVal;
        }
        else if(sign == '/'){
            if(nextVal == 0){
                newVal = 0;
            }
            else{
                newVal =  currenctVal/nextVal;
            }
        }

        return newVal;
    }

}


Some testing...

System.assertEquals(11, StringToNumber.convertStringToNum('5+6'));
System.assertEquals(1, StringToNumber.convertStringToNum('-5+6'));
System.assertEquals(-0.4, StringToNumber.convertStringToNum('-0.8/2'));
System.assertEquals(-18, StringToNumber.convertStringToNum('6*(-3)'));
System.assertEquals(9, StringToNumber.convertStringToNum('6*(-3)/-2'));
System.assertEquals(-29, StringToNumber.convertStringToNum('-2*(6*(-3)/-2+5.5)'));
System.assertEquals(24, StringToNumber.convertStringToNum('(5+(6/2))*3'));
System.assertEquals(-6, StringToNumber.convertStringToNum('(5+(14/-2))*3'));
System.assertEquals(124, StringToNumber.convertStringToNum('(5+(6/2))*3+100'));
System.assertEquals(74, StringToNumber.convertStringToNum('(5+(6/2))*3+(100/2)'));
System.assertEquals(740, StringToNumber.convertStringToNum('((5+(6/2))*3+(100/2))*10'));


Retire of Permission on Profiles

If you are working as a Salesforce admin/developer you've probably heard somewhere that Salesforce is planning to make a significant cha...