Skip to content

Commit 1468925

Browse files
committed
Support String index type in custom IndexAccessor
Closes gh-32706
1 parent a3d3bc0 commit 1468925

File tree

2 files changed

+75
-8
lines changed

2 files changed

+75
-8
lines changed

spring-expression/src/main/java/org/springframework/expression/spel/ast/Indexer.java

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -244,14 +244,7 @@ else if (target instanceof Collection<?> collection) {
244244
}
245245
}
246246

247-
// Try to treat the index value as a property of the context object.
248-
TypeDescriptor valueType = indexValue.getTypeDescriptor();
249-
if (valueType != null && String.class == valueType.getType()) {
250-
this.indexedType = IndexedType.OBJECT;
251-
return new PropertyAccessorValueRef(
252-
target, (String) index, state.getEvaluationContext(), targetDescriptor);
253-
}
254-
247+
// Check for a custom IndexAccessor.
255248
EvaluationContext evalContext = state.getEvaluationContext();
256249
List<IndexAccessor> accessorsToTry = getIndexAccessorsToTry(target, evalContext.getIndexAccessors());
257250
if (accessMode.supportsReads) {
@@ -285,6 +278,14 @@ else if (target instanceof Collection<?> collection) {
285278
}
286279
}
287280

281+
// As a last resort, try to treat the index value as a property of the context object.
282+
TypeDescriptor valueType = indexValue.getTypeDescriptor();
283+
if (valueType != null && String.class == valueType.getType()) {
284+
this.indexedType = IndexedType.OBJECT;
285+
return new PropertyAccessorValueRef(
286+
target, (String) index, state.getEvaluationContext(), targetDescriptor);
287+
}
288+
288289
throw new SpelEvaluationException(
289290
getStartPosition(), SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE, targetDescriptor);
290291
}

spring-expression/src/test/java/org/springframework/expression/spel/IndexingTests.java

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -711,6 +711,72 @@ void readAndWriteIndexWithSimpleEvaluationContext() {
711711
assertThat(expr.getValue(context, arrayNode)).isSameAs(node1);
712712
}
713713

714+
@Test // gh-32706
715+
void readIndexWithStringIndexType() {
716+
BirdNameToColorMappings birdNameMappings = new BirdNameToColorMappings();
717+
718+
// Without a registered BirdNameToColorMappingsIndexAccessor, we should
719+
// be able to index into an object via a property name.
720+
Expression propertyExpression = parser.parseExpression("['property']");
721+
assertThat(propertyExpression.getValue(context, birdNameMappings)).isEqualTo("enigma");
722+
723+
context.addIndexAccessor(new BirdNameToColorMappingsIndexAccessor());
724+
725+
Expression expression = parser.parseExpression("['cardinal']");
726+
assertThat(expression.getValue(context, birdNameMappings)).isEqualTo(Color.RED);
727+
728+
// With a registered BirdNameToColorMappingsIndexAccessor, an attempt
729+
// to index into an object via a property name should fail.
730+
assertThatExceptionOfType(SpelEvaluationException.class)
731+
.isThrownBy(() -> propertyExpression.getValue(context, birdNameMappings))
732+
.withMessageEndingWith("A problem occurred while attempting to read index '%s' in '%s'",
733+
"property", BirdNameToColorMappings.class.getName())
734+
.havingCause().withMessage("unknown bird color: property");
735+
}
736+
737+
static class BirdNameToColorMappings {
738+
739+
public final String property = "enigma";
740+
741+
public Color get(String name) {
742+
return switch (name) {
743+
case "cardinal" -> Color.RED;
744+
case "blue jay" -> Color.BLUE;
745+
default -> throw new RuntimeException("unknown bird color: " + name);
746+
};
747+
}
748+
}
749+
750+
static class BirdNameToColorMappingsIndexAccessor implements IndexAccessor {
751+
752+
@Override
753+
public Class<?>[] getSpecificTargetClasses() {
754+
return new Class[] { BirdNameToColorMappings.class };
755+
}
756+
757+
@Override
758+
public boolean canRead(EvaluationContext context, Object target, Object index) {
759+
return (target instanceof BirdNameToColorMappings && index instanceof String);
760+
}
761+
762+
@Override
763+
public TypedValue read(EvaluationContext context, Object target, Object index) {
764+
BirdNameToColorMappings mappings = (BirdNameToColorMappings) target;
765+
String name = (String) index;
766+
return new TypedValue(mappings.get(name));
767+
}
768+
769+
@Override
770+
public boolean canWrite(EvaluationContext context, Object target, Object index) {
771+
return false;
772+
}
773+
774+
@Override
775+
public void write(EvaluationContext context, Object target, Object index, @Nullable Object newValue) {
776+
throw new UnsupportedOperationException();
777+
}
778+
}
779+
714780
/**
715781
* {@link IndexAccessor} that knows how to read and write indexes in a
716782
* Jackson {@link ArrayNode}.

0 commit comments

Comments
 (0)