Test SpEL Map access/indexing support for nonexistent keys
Build and Deploy Snapshot / Build and Deploy Snapshot (push) Waiting to run Details
Build and Deploy Snapshot / Verify (push) Blocked by required conditions Details
Deploy Docs / Dispatch docs deployment (push) Waiting to run Details

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
This commit is contained in:
Sam Brannen 2025-10-04 17:19:44 +02:00
parent eb11070c19
commit d81f1a55c2
1 changed files with 46 additions and 0 deletions

View File

@ -23,8 +23,11 @@ import org.junit.jupiter.api.Test;
import org.springframework.expression.EvaluationContext; import org.springframework.expression.EvaluationContext;
import org.springframework.expression.TypedValue; import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.standard.SpelExpressionParser; 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.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. * Testing variations on map access.
@ -43,6 +46,11 @@ class MapAccessTests extends AbstractExpressionTests {
evaluate("testMap['monday']", "montag", String.class); evaluate("testMap['monday']", "montag", String.class);
} }
@Test
void mapAccessThroughIndexerForNonexistentKey() {
evaluate("testMap['bogus']", null, String.class);
}
@Test @Test
void variableMapAccess() { void variableMapAccess() {
var parser = new SpelExpressionParser(); var parser = new SpelExpressionParser();
@ -80,10 +88,48 @@ class MapAccessTests extends AbstractExpressionTests {
var expr1 = parser.parseExpression("testMap.monday"); var expr1 = parser.parseExpression("testMap.monday");
assertThat(expr1.getValue(ctx, String.class)).isEqualTo("montag"); 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<String, String> properties, TestBean nestedBean) { record TestBean(Map<String, String> 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));
}
}
} }