From d81f1a55c25e1c4783beb7e415380063c1c514b2 Mon Sep 17 00:00:00 2001 From: Sam Brannen <104798+sbrannen@users.noreply.github.com> Date: Sat, 4 Oct 2025 17:19:44 +0200 Subject: [PATCH] Test SpEL Map access/indexing support for nonexistent keys This commit introduces two tests to verify the status quo. - mapAccessThroughIndexerForNonexistentKey(): demonstrates that map access via the built-in support in the Indexer returns `null` for a nonexistent key. - nullAwareMapAccessor(): demonstrates that users can implement and register a custom extension of MapAccessor which reports that it can read any map (ignoring whether the map actually contains an entry for the given key) and returns `null` for a nonexistent key. See gh-35534 --- .../expression/spel/MapAccessTests.java | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/MapAccessTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/MapAccessTests.java index 7f9a5153e8d..5e2e8ddef25 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/MapAccessTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/MapAccessTests.java @@ -23,8 +23,11 @@ import org.junit.jupiter.api.Test; import org.springframework.expression.EvaluationContext; import org.springframework.expression.TypedValue; import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.MapAccessor; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.springframework.expression.spel.SpelMessage.PROPERTY_OR_FIELD_NOT_READABLE; /** * Testing variations on map access. @@ -43,6 +46,11 @@ class MapAccessTests extends AbstractExpressionTests { evaluate("testMap['monday']", "montag", String.class); } + @Test + void mapAccessThroughIndexerForNonexistentKey() { + evaluate("testMap['bogus']", null, String.class); + } + @Test void variableMapAccess() { var parser = new SpelExpressionParser(); @@ -80,10 +88,48 @@ class MapAccessTests extends AbstractExpressionTests { var expr1 = parser.parseExpression("testMap.monday"); assertThat(expr1.getValue(ctx, String.class)).isEqualTo("montag"); + + var expr2 = parser.parseExpression("testMap.bogus"); + assertThatExceptionOfType(SpelEvaluationException.class) + .isThrownBy(() -> expr2.getValue(ctx, String.class)) + .satisfies(ex -> assertThat(ex.getMessageCode()).isEqualTo(PROPERTY_OR_FIELD_NOT_READABLE)); + } + + @Test + void nullAwareMapAccessor() { + var parser = new SpelExpressionParser(); + var ctx = TestScenarioCreator.getTestEvaluationContext(); + ctx.addPropertyAccessor(new NullAwareMapAccessor()); + + var expr = parser.parseExpression("testMap.monday"); + assertThat(expr.getValue(ctx, String.class)).isEqualTo("montag"); + + // Unlike MapAccessor, NullAwareMapAccessor returns null for a nonexistent key. + expr = parser.parseExpression("testMap.bogus"); + assertThat(expr.getValue(ctx, String.class)).isNull(); } record TestBean(Map properties, TestBean nestedBean) { } + + /** + * In contrast to the standard {@link MapAccessor}, {@code NullAwareMapAccessor} + * reports that it can read any map (ignoring whether the map actually contains + * an entry for the given key) and returns {@code null} for a nonexistent key. + */ + private static class NullAwareMapAccessor extends MapAccessor { + + @Override + public boolean canRead(EvaluationContext context, Object target, String name) { + return (target instanceof Map); + } + + @Override + public TypedValue read(EvaluationContext context, Object target, String name) { + return new TypedValue(((Map) target).get(name)); + } + } + }