Passing map object as root object in Spring Expression Language (SpEL)
Tags: spring
Published: 2024-02-11 13:56:59

## Notice

I wrote this article and was originally published on [Qiita](https://qiita.com/saladlam/items/eb7d94deda13bfa15dd3) on 9 September 2019.

---

# What is root object

From [Spring Framework Documentation](https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#expressions), states that "an expression string that is evaluated against a specific object instance (called the root object)".

Example below is also extracted from same document. Assume class "Inventor" is defined as

```java
import java.util.Date;
import java.util.GregorianCalendar;

public class Inventor {

    private String name;
    private String nationality;
    private String[] inventions;
    private Date birthdate;
    private PlaceOfBirth placeOfBirth;

    public Inventor(String name, String nationality) {
        GregorianCalendar c= new GregorianCalendar();
        this.name = name;
        this.nationality = nationality;
        this.birthdate = c.getTime();
    }

    public Inventor(String name, Date birthdate, String nationality) {
        this.name = name;
        this.nationality = nationality;
        this.birthdate = birthdate;
    }

    public Inventor() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getNationality() {
        return nationality;
    }

    public void setNationality(String nationality) {
        this.nationality = nationality;
    }

    public Date getBirthdate() {
        return birthdate;
    }

    public void setBirthdate(Date birthdate) {
        this.birthdate = birthdate;
    }

    public PlaceOfBirth getPlaceOfBirth() {
        return placeOfBirth;
    }

    public void setPlaceOfBirth(PlaceOfBirth placeOfBirth) {
        this.placeOfBirth = placeOfBirth;
    }

    public void setInventions(String[] inventions) {
        this.inventions = inventions;
    }

    public String[] getInventions() {
        return inventions;
    }
}
```

"Root object" should be provided when evaluates an expression.

```java
Inventor tesla = new Inventor("Nikola Tesla", c.getTime(), "Serbian");

ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("name"); 
// parameter pass into method getValue() is called "root object"
String name = (String) exp.getValue(tesla);
// name is "Nikola Tesla"
```

# Passing map object as root object

Below is example

```java
import java.util.HashMap;
import java.util.Map;

import org.springframework.context.expression.MapAccessor;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

public class SpelRootObjectExample {

	public static void main(String[] args) {
		Map<String, String> map = new HashMap<>();
		map.put("a", "String 'a'");
		map.put("b", "String 'b'");

		StandardEvaluationContext context = new StandardEvaluationContext();
		context.addPropertyAccessor(new MapAccessor());
		ExpressionParser parser = new SpelExpressionParser();
		Expression expA = parser.parseExpression("a");
		Expression expB = parser.parseExpression("b");

		// root object is map
		System.out.println(expA.getValue(context, map));
		System.out.println(expB.getValue(context, map));
	}

}
```

Output is

```
String 'a'
String 'b'
```

# Where can find this usage?

In Thymeleaf template engine which used in Spring MVC, similar code can be found on class "org.thymeleaf.spring5.expression.ThymeleafEvaluationContext".

```java
public final class ThymeleafEvaluationContext
            extends StandardEvaluationContext
            implements IThymeleafEvaluationContext {

    public static final String THYMELEAF_EVALUATION_CONTEXT_CONTEXT_VARIABLE_NAME = "thymeleaf::EvaluationContext";
    private static final MapAccessor MAP_ACCESSOR_INSTANCE = new MapAccessor();
    private final ApplicationContext applicationContext;
    private IExpressionObjects expressionObjects = null;
    private boolean variableAccessRestricted = false;

    public ThymeleafEvaluationContext(final ApplicationContext applicationContext, final ConversionService conversionService) {
        super();

        Validate.notNull(applicationContext, "Application Context cannot be null");
        // ConversionService CAN be null

        this.applicationContext = applicationContext;
        this.setBeanResolver(new BeanFactoryResolver(applicationContext));
        if (conversionService != null) {
            this.setTypeConverter(new StandardTypeConverter(conversionService));
        }

        this.addPropertyAccessor(SPELContextPropertyAccessor.INSTANCE);
		// HERE!!
        this.addPropertyAccessor(MAP_ACCESSOR_INSTANCE);

    }
	// ...
}
```

On method org.thymeleaf.spring5.expression.SPELVariableExpressionEvaluator.evaluate(IExpressionContext, IStandardVariableExpression, StandardExpressionExecutionContext), class SPELContextMapWrapper (implementation of java.util.Map interface) act as root object and be used for evaluate an expression.

```java
            final IThymeleafEvaluationContext thymeleafEvaluationContext = (IThymeleafEvaluationContext) evaluationContext;

            /*
             * CONFIGURE THE IThymeleafEvaluationContext INSTANCE: expression objects and restrictions
             *
             * NOTE this is possible even if the evaluation context object is shared for the whole template execution
             * because evaluation contexts are not thread-safe and are only used in a single template execution
             */
            thymeleafEvaluationContext.setExpressionObjects(expressionObjects);
            thymeleafEvaluationContext.setVariableAccessRestricted(expContext.getRestrictVariableAccess());

            /*
             * RESOLVE THE EVALUATION ROOT
             */
            final ITemplateContext templateContext = (context instanceof ITemplateContext ? (ITemplateContext) context : null);
            final Object evaluationRoot =
                    (useSelectionAsRoot && templateContext != null && templateContext.hasSelectionTarget()?
                            templateContext.getSelectionTarget() : new SPELContextMapWrapper(context, thymeleafEvaluationContext));

            /*
             * If no conversion is to be made, JUST RETURN
             */
            if (!expContext.getPerformTypeConversion()) {
                return exp.expression.getValue(thymeleafEvaluationContext, evaluationRoot);
            }
```