SpelEvaluationException: EL1001E: Type conversion problem

Viewed 117

I am using SpEL (Spring Expression Language) to evaluate a templated String using code that looks something like this:

Expression exp = parser.parseExpression(templatedString, templateParserContext);
return exp.getValue(readOnlyEvalContext, contextRoot);

When templatedString == "flow.config: ${flow.config}", and "flow.config" in contextRoot is a LinkedHashMap<String, Object> (created by Jackson during JSON deserialization) I get the following exception:

org.springframework.expression.spel.SpelEvaluationException: EL1001E: Type conversion problem, cannot convert from java.util.LinkedHashMap<?, ?> to java.lang.String

To solve this I tried to create a converter to use ObjectMapper to serialize the LinkedHashMap so that I can show inline JSON for the Map in my evaluated expressions.

@Configuration
public class ConverterConfig implements WebMvcConfigurer {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new ConverterLinkedHashMapToString());
    }
}
@Component
public class ConverterLinkedHashMapToString implements Converter<LinkedHashMap<?, ?>, String> {

    @Override
    public String convert(LinkedHashMap<?, ?> source) {
        return objMapper.writeValueAsString(source);
    }

    @Override
    public <U> Converter<LinkedHashMap<?, ?>, U> andThen(Converter<? super String, ? extends U> after) {
        return Converter.super.andThen(after);
    }
}

No matter what I do, I can't seem to get this to work. I just keep getting the same exception. I'm probably doing something wrong, but despite about a day of searching and struggling, I haven't been able to resolve the problem. Any help would be greatly appreciated! Thanks!

Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1001E: Type conversion problem, cannot convert from java.util.LinkedHashMap<?, ?> to java.lang.String
    at org.springframework.expression.spel.support.StandardTypeConverter.convertValue(StandardTypeConverter.java:87) ~[spring-expression-5.3.22.jar:5.3.22]
    at org.springframework.expression.common.ExpressionUtils.convertTypedValue(ExpressionUtils.java:57) ~[spring-expression-5.3.22.jar:5.3.22]
    at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:378) ~[spring-expression-5.3.22.jar:5.3.22]
    at org.springframework.expression.common.CompositeStringExpression.getValue(CompositeStringExpression.java:129) ~[spring-expression-5.3.22.jar:5.3.22]
    at org.springframework.expression.common.CompositeStringExpression.getValue(CompositeStringExpression.java:43) ~[spring-expression-5.3.22.jar:5.3.22]

[...]

    ... 3 common frames omitted
Caused by: org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.util.LinkedHashMap<?, ?>] to type [java.lang.String]
    at org.springframework.core.convert.support.GenericConversionService.handleConverterNotFound(GenericConversionService.java:322) ~[spring-core-5.3.22.jar:5.3.22]
    at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:195) ~[spring-core-5.3.22.jar:5.3.22]
    at org.springframework.expression.spel.support.StandardTypeConverter.convertValue(StandardTypeConverter.java:82) ~[spring-expression-5.3.22.jar:5.3.22]
    ... 38 common frames omitted
1 Answers

I was able to get it working with your converter by adding the Converter to the EvaluationContext through a TypeConverter. The code to add the Converter to EvaluationContext looks like below:

GenericConversionService conversionService = new DefaultConversionService();
conversionService.addConverter(new ConverterLinkedHashMapToString());
TypeConverter typeConverter = new StandardTypeConverter(conversionService);
StandardEvaluationContext evalContext = new StandardEvaluationContext();
evalContext.setTypeConverter(typeConverter);

Expression exp = parser.parseExpression(templatedString, templateParserContext);
String message = (String) exp.getValue(evalContext, contextRoot);
System.out.println(message);

I didn't test this in a spring/spring-boot application though. I tested using a standalone groovy script. The complete script below for reference:

@Grab(group='org.springframework', module='spring-expression', version='5.3.22')
@Grab(group='com.fasterxml.jackson.core', module='jackson-databind', version='2.13.3')

import com.fasterxml.jackson.databind.ObjectMapper;

import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.expression.spel.support.StandardTypeConverter;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParserContext;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.TypeConverter;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.support.DefaultConversionService;

ExpressionParser parser = new SpelExpressionParser();
ParserContext templateParserContext = new TemplateParserContext('${', '}');
String templatedString = "flow.config: \${flow['config']}";
Dummy contextRoot = new Dummy();

GenericConversionService conversionService = new DefaultConversionService();
conversionService.addConverter(new ConverterLinkedHashMapToString());
TypeConverter typeConverter = new StandardTypeConverter(conversionService);
StandardEvaluationContext evalContext = new StandardEvaluationContext();
evalContext.setTypeConverter(typeConverter);

Expression exp = parser.parseExpression(templatedString, templateParserContext);
String message = (String) exp.getValue(evalContext, contextRoot);
System.out.println(message);

class Dummy {
    Map<String, ?> flow = ['config': ['hola': 'hello']];
}

class ConverterLinkedHashMapToString implements Converter<LinkedHashMap<?, ?>, String> {
    private ObjectMapper objMapper = new ObjectMapper();

    @Override
    public String convert(LinkedHashMap<?, ?> source) {
        return objMapper.writeValueAsString(source);
    }
}

Before adding the Converter to the EvaluationContext, I got the same SpelEvaluationException. After adding the converter, the code runs fine and prints the parsed String containing the JSON value of the map as flow.config: {"hola":"hello"}

Related