Issue
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
Solution
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"}
Answered By - devatherock
Answer Checked By - Candace Johnson (JavaFixing Volunteer)