Skip to content

Commit fdbefad

Browse files
committedMar 1, 2024
Improve documentation for SpEL indexing support
Prior to this commit, the reference manual only documented indexing support for arrays, lists, and maps. This commit improves the overall documentation for SpEL's property navigation and indexing support and introduces additional documentation for indexing into Strings and Objects. Closes gh-32355
1 parent bdcd10e commit fdbefad

File tree

2 files changed

+186
-41
lines changed

2 files changed

+186
-41
lines changed
 

Diff for: ‎framework-docs/modules/ROOT/pages/core/expressions/language-ref/properties-arrays.adoc

+132-26
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
11
[[expressions-properties-arrays]]
22
= Properties, Arrays, Lists, Maps, and Indexers
33

4-
Navigating with property references is easy. To do so, use a period to indicate a nested
5-
property value. The instances of the `Inventor` class, `pupin` and `tesla`, were
6-
populated with data listed in the xref:core/expressions/example-classes.adoc[Classes used in the examples]
7-
section. To navigate "down" the object graph and get Tesla's year of birth and
8-
Pupin's city of birth, we use the following expressions:
4+
The Spring Expression Language provides support for navigating object graphs and indexing
5+
into various structures.
6+
7+
NOTE: Numerical index values are zero-based, such as when accessing the n^th^ element of
8+
an array in Java.
9+
10+
[[expressions-property-navigation]]
11+
== Property Navigation
12+
13+
You can navigate property references within an object graph by using a period to indicate
14+
a nested property value. The instances of the `Inventor` class, `pupin` and `tesla`, were
15+
populated with data listed in the
16+
xref:core/expressions/example-classes.adoc[Classes used in the examples] section. To
17+
navigate _down_ the object graph and get Tesla's year of birth and Pupin's city of birth,
18+
we use the following expressions:
919

1020
[tabs]
1121
======
@@ -16,6 +26,7 @@ Java::
1626
// evaluates to 1856
1727
int year = (Integer) parser.parseExpression("birthdate.year + 1900").getValue(context);
1828
29+
// evaluates to "Smiljan"
1930
String city = (String) parser.parseExpression("placeOfBirth.city").getValue(context);
2031
----
2132
@@ -26,6 +37,7 @@ Kotlin::
2637
// evaluates to 1856
2738
val year = parser.parseExpression("birthdate.year + 1900").getValue(context) as Int
2839
40+
// evaluates to "Smiljan"
2941
val city = parser.parseExpression("placeOfBirth.city").getValue(context) as String
3042
----
3143
======
@@ -39,8 +51,20 @@ method invocations -- for example, `getPlaceOfBirth().getCity()` instead of
3951
`placeOfBirth.city`.
4052
====
4153

42-
The contents of arrays and lists are obtained by using square bracket notation, as the
43-
following example shows:
54+
[[expressions-indexing-arrays-and-collections]]
55+
== Indexing into Arrays and Collections
56+
57+
The n^th^ element of an array or collection (for example, a `Set` or `List`) can be
58+
obtained by using square bracket notation, as the following example shows.
59+
60+
[NOTE]
61+
====
62+
If the indexed collection is a `java.util.List`, the n^th^ element will be accessed
63+
directly via `list.get(n)`.
64+
65+
For any other type of `Collection`, the n^th^ element will be accessed by iterating over
66+
the collection using its `Iterator` and returning the n^th^ element encountered.
67+
====
4468

4569
[tabs]
4670
======
@@ -63,7 +87,8 @@ Java::
6387
String name = parser.parseExpression("members[0].name").getValue(
6488
context, ieee, String.class);
6589
66-
// List and Array navigation
90+
// List and Array Indexing
91+
6792
// evaluates to "Wireless communication"
6893
String invention = parser.parseExpression("members[0].inventions[6]").getValue(
6994
context, ieee, String.class);
@@ -88,55 +113,136 @@ Kotlin::
88113
val name = parser.parseExpression("members[0].name").getValue(
89114
context, ieee, String::class.java)
90115
91-
// List and Array navigation
116+
// List and Array Indexing
117+
92118
// evaluates to "Wireless communication"
93119
val invention = parser.parseExpression("members[0].inventions[6]").getValue(
94120
context, ieee, String::class.java)
95121
----
96122
======
97123

98-
The contents of maps are obtained by specifying the literal key value within the
99-
brackets. In the following example, because keys for the `officers` map are strings, we can specify
100-
string literals:
124+
[[expressions-indexing-strings]]
125+
== Indexing into Strings
126+
127+
The n^th^ character of a string can be obtained by specifying the index within square
128+
brackets, as demonstrated in the following example.
129+
130+
NOTE: The n^th^ character of a string will evaluate to a `java.lang.String`, not a
131+
`java.lang.Character`.
101132

102133
[tabs]
103134
======
104135
Java::
105136
+
106137
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
107138
----
108-
// Officer's Dictionary
139+
// evaluates to "T" (8th letter of "Nikola Tesla")
140+
String character = parser.parseExpression("members[0].name[7]")
141+
.getValue(societyContext, String.class);
142+
----
109143
110-
Inventor pupin = parser.parseExpression("officers['president']").getValue(
111-
societyContext, Inventor.class);
144+
Kotlin::
145+
+
146+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
147+
----
148+
// evaluates to "T" (8th letter of "Nikola Tesla")
149+
val character = parser.parseExpression("members[0].name[7]")
150+
.getValue(societyContext, String::class.java)
151+
----
152+
======
153+
154+
[[expressions-indexing-maps]]
155+
== Indexing into Maps
156+
157+
The contents of maps are obtained by specifying the key value within square brackets. In
158+
the following example, because keys for the `officers` map are strings, we can specify
159+
string literals such as `'president'`:
160+
161+
[tabs]
162+
======
163+
Java::
164+
+
165+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
166+
----
167+
// Officer's Map
168+
169+
// evaluates to Inventor("Pupin")
170+
Inventor pupin = parser.parseExpression("officers['president']")
171+
.getValue(societyContext, Inventor.class);
112172
113173
// evaluates to "Idvor"
114-
String city = parser.parseExpression("officers['president'].placeOfBirth.city").getValue(
115-
societyContext, String.class);
174+
String city = parser.parseExpression("officers['president'].placeOfBirth.city")
175+
.getValue(societyContext, String.class);
176+
177+
String countryExpression = "officers['advisors'][0].placeOfBirth.country";
116178
117179
// setting values
118-
parser.parseExpression("officers['advisors'][0].placeOfBirth.country").setValue(
119-
societyContext, "Croatia");
180+
parser.parseExpression(countryExpression)
181+
.setValue(societyContext, "Croatia");
182+
183+
// evaluates to "Croatia"
184+
String country = parser.parseExpression(countryExpression)
185+
.getValue(societyContext, String.class);
120186
----
121187
122188
Kotlin::
123189
+
124190
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
125191
----
126-
// Officer's Dictionary
192+
// Officer's Map
127193
128-
val pupin = parser.parseExpression("officers['president']").getValue(
129-
societyContext, Inventor::class.java)
194+
// evaluates to Inventor("Pupin")
195+
val pupin = parser.parseExpression("officers['president']")
196+
.getValue(societyContext, Inventor::class.java)
130197
131198
// evaluates to "Idvor"
132-
val city = parser.parseExpression("officers['president'].placeOfBirth.city").getValue(
133-
societyContext, String::class.java)
199+
val city = parser.parseExpression("officers['president'].placeOfBirth.city")
200+
.getValue(societyContext, String::class.java)
201+
202+
val countryExpression = "officers['advisors'][0].placeOfBirth.country"
134203
135204
// setting values
136-
parser.parseExpression("officers['advisors'][0].placeOfBirth.country").setValue(
137-
societyContext, "Croatia")
205+
parser.parseExpression(countryExpression)
206+
.setValue(societyContext, "Croatia")
207+
208+
// evaluates to "Croatia"
209+
val country = parser.parseExpression(countryExpression)
210+
.getValue(societyContext, String::class.java)
138211
----
139212
======
140213

214+
[[expressions-indexing-objects]]
215+
== Indexing into Objects
216+
217+
A property of an object can be obtained by specifying the name of the property within
218+
square brackets. This is analogous to accessing the value of a map based on its key. The
219+
following example demonstrates how to _index_ into an object to retrieve a specific
220+
property.
221+
222+
[tabs]
223+
======
224+
Java::
225+
+
226+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
227+
----
228+
// Create an inventor to use as the root context object.
229+
Inventor tesla = new Inventor("Nikola Tesla");
230+
231+
// evaluates to "Nikola Tesla"
232+
String name = parser.parseExpression("#root['name']")
233+
.getValue(context, tesla, String.class);
234+
----
141235
236+
Kotlin::
237+
+
238+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
239+
----
240+
// Create an inventor to use as the root context object.
241+
val tesla = Inventor("Nikola Tesla")
242+
243+
// evaluates to "Nikola Tesla"
244+
val name = parser.parseExpression("#root['name']")
245+
.getValue(context, tesla, String::class.java)
246+
----
247+
======
142248

Diff for: ‎spring-expression/src/test/java/org/springframework/expression/spel/SpelDocumentationTests.java

+54-15
Original file line numberDiff line numberDiff line change
@@ -161,60 +161,99 @@ void literals() {
161161
class PropertiesArraysListsMapsAndIndexers {
162162

163163
@Test
164-
void propertyAccess() {
164+
void propertyNavigation() {
165165
EvaluationContext context = TestScenarioCreator.getTestEvaluationContext();
166+
167+
// evaluates to 1856
166168
int year = (Integer) parser.parseExpression("Birthdate.Year + 1900").getValue(context); // 1856
167169
assertThat(year).isEqualTo(1856);
168170

171+
// evaluates to "Smiljan"
169172
String city = (String) parser.parseExpression("placeOfBirth.City").getValue(context);
170173
assertThat(city).isEqualTo("Smiljan");
171174
}
172175

173176
@Test
174-
void propertyNavigation() {
177+
void indexingIntoArraysAndCollections() {
175178
ExpressionParser parser = new SpelExpressionParser();
176179
StandardEvaluationContext teslaContext = TestScenarioCreator.getTestEvaluationContext();
180+
StandardEvaluationContext societyContext = new StandardEvaluationContext();
181+
societyContext.setRootObject(new IEEE());
177182

178183
// Inventions Array
184+
179185
// evaluates to "Induction motor"
180186
String invention = parser.parseExpression("inventions[3]").getValue(teslaContext, String.class);
181187
assertThat(invention).isEqualTo("Induction motor");
182188

183189
// Members List
184-
StandardEvaluationContext societyContext = new StandardEvaluationContext();
185-
societyContext.setRootObject(new IEEE());
186190

187191
// evaluates to "Nikola Tesla"
188192
String name = parser.parseExpression("members[0].Name").getValue(societyContext, String.class);
189193
assertThat(name).isEqualTo("Nikola Tesla");
190194

191-
// List and Array navigation
195+
// List and Array Indexing
196+
192197
// evaluates to "Wireless communication"
193198
invention = parser.parseExpression("members[0].Inventions[6]").getValue(societyContext, String.class);
194199
assertThat(invention).isEqualTo("Wireless communication");
195200
}
196201

197202
@Test
198-
void maps() {
203+
void indexingIntoStrings() {
204+
ExpressionParser parser = new SpelExpressionParser();
205+
StandardEvaluationContext societyContext = new StandardEvaluationContext();
206+
societyContext.setRootObject(new IEEE());
207+
208+
// evaluates to "T" (8th letter of "Nikola Tesla")
209+
String character = parser.parseExpression("members[0].name[7]")
210+
.getValue(societyContext, String.class);
211+
assertThat(character).isEqualTo("T");
212+
}
213+
214+
@Test
215+
void indexingIntoMaps() {
199216
StandardEvaluationContext societyContext = new StandardEvaluationContext();
200217
societyContext.setRootObject(new IEEE());
201-
// Officer's map
202-
Inventor pupin = parser.parseExpression("officers['president']").getValue(societyContext, Inventor.class);
218+
219+
// Officer's Map
220+
221+
// evaluates to Inventor("Pupin")
222+
Inventor pupin = parser.parseExpression("officers['president']")
223+
.getValue(societyContext, Inventor.class);
203224
assertThat(pupin).isNotNull();
225+
assertThat(pupin.getName()).isEqualTo("Pupin");
204226

205227
// evaluates to "Idvor"
206-
String city = parser.parseExpression("officers['president'].PlaceOfBirth.city").getValue(societyContext, String.class);
207-
assertThat(city).isNotNull();
228+
String city = parser.parseExpression("officers['president'].placeOfBirth.city")
229+
.getValue(societyContext, String.class);
230+
assertThat(city).isEqualTo("Idvor");
231+
232+
String countryExpression = "officers['advisors'][0].placeOfBirth.Country";
208233

209234
// setting values
210-
Inventor i = parser.parseExpression("officers['advisors'][0]").getValue(societyContext, Inventor.class);
211-
assertThat(i.getName()).isEqualTo("Nikola Tesla");
235+
parser.parseExpression(countryExpression)
236+
.setValue(societyContext, "Croatia");
237+
238+
// evaluates to "Croatia"
239+
String country = parser.parseExpression(countryExpression)
240+
.getValue(societyContext, String.class);
241+
assertThat(country).isEqualTo("Croatia");
242+
}
243+
244+
@Test
245+
void indexingIntoObjects() {
246+
ExpressionParser parser = new SpelExpressionParser();
212247

213-
parser.parseExpression("officers['advisors'][0].PlaceOfBirth.Country").setValue(societyContext, "Croatia");
248+
// Create an inventor to use as the root context object.
249+
Inventor tesla = new Inventor("Nikola Tesla");
214250

215-
Inventor i2 = parser.parseExpression("reverse[0]['advisors'][0]").getValue(societyContext, Inventor.class);
216-
assertThat(i2.getName()).isEqualTo("Nikola Tesla");
251+
// evaluates to "Nikola Tesla"
252+
String name = parser.parseExpression("#root['name']")
253+
.getValue(context, tesla, String.class);
254+
assertThat(name).isEqualTo("Nikola Tesla");
217255
}
256+
218257
}
219258

220259
@Nested

0 commit comments

Comments
 (0)
Please sign in to comment.