diff --git a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/properties-arrays.adoc b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/properties-arrays.adoc index 3066b54dec..8e8360d1da 100644 --- a/framework-docs/modules/ROOT/pages/core/expressions/language-ref/properties-arrays.adoc +++ b/framework-docs/modules/ROOT/pages/core/expressions/language-ref/properties-arrays.adoc @@ -1,11 +1,21 @@ [[expressions-properties-arrays]] = Properties, Arrays, Lists, Maps, and Indexers -Navigating with property references is easy. To do so, use a period to indicate a nested -property value. The instances of the `Inventor` class, `pupin` and `tesla`, were -populated with data listed in the xref:core/expressions/example-classes.adoc[Classes used in the examples] - section. To navigate "down" the object graph and get Tesla's year of birth and -Pupin's city of birth, we use the following expressions: +The Spring Expression Language provides support for navigating object graphs and indexing +into various structures. + +NOTE: Numerical index values are zero-based, such as when accessing the n^th^ element of +an array in Java. + +[[expressions-property-navigation]] +== Property Navigation + +You can navigate property references within an object graph by using a period to indicate +a nested property value. The instances of the `Inventor` class, `pupin` and `tesla`, were +populated with data listed in the +xref:core/expressions/example-classes.adoc[Classes used in the examples] section. To +navigate _down_ the object graph and get Tesla's year of birth and Pupin's city of birth, +we use the following expressions: [tabs] ====== @@ -16,6 +26,7 @@ Java:: // evaluates to 1856 int year = (Integer) parser.parseExpression("birthdate.year + 1900").getValue(context); + // evaluates to "Smiljan" String city = (String) parser.parseExpression("placeOfBirth.city").getValue(context); ---- @@ -26,6 +37,7 @@ Kotlin:: // evaluates to 1856 val year = parser.parseExpression("birthdate.year + 1900").getValue(context) as Int + // evaluates to "Smiljan" val city = parser.parseExpression("placeOfBirth.city").getValue(context) as String ---- ====== @@ -39,8 +51,20 @@ method invocations -- for example, `getPlaceOfBirth().getCity()` instead of `placeOfBirth.city`. ==== -The contents of arrays and lists are obtained by using square bracket notation, as the -following example shows: +[[expressions-indexing-arrays-and-collections]] +== Indexing into Arrays and Collections + +The n^th^ element of an array or collection (for example, a `Set` or `List`) can be +obtained by using square bracket notation, as the following example shows. + +[NOTE] +==== +If the indexed collection is a `java.util.List`, the n^th^ element will be accessed +directly via `list.get(n)`. + +For any other type of `Collection`, the n^th^ element will be accessed by iterating over +the collection using its `Iterator` and returning the n^th^ element encountered. +==== [tabs] ====== @@ -63,7 +87,8 @@ Java:: String name = parser.parseExpression("members[0].name").getValue( context, ieee, String.class); - // List and Array navigation + // List and Array Indexing + // evaluates to "Wireless communication" String invention = parser.parseExpression("members[0].inventions[6]").getValue( context, ieee, String.class); @@ -88,16 +113,22 @@ Kotlin:: val name = parser.parseExpression("members[0].name").getValue( context, ieee, String::class.java) - // List and Array navigation + // List and Array Indexing + // evaluates to "Wireless communication" val invention = parser.parseExpression("members[0].inventions[6]").getValue( context, ieee, String::class.java) ---- ====== -The contents of maps are obtained by specifying the literal key value within the -brackets. In the following example, because keys for the `officers` map are strings, we can specify -string literals: +[[expressions-indexing-strings]] +== Indexing into Strings + +The n^th^ character of a string can be obtained by specifying the index within square +brackets, as demonstrated in the following example. + +NOTE: The n^th^ character of a string will evaluate to a `java.lang.String`, not a +`java.lang.Character`. [tabs] ====== @@ -105,38 +136,113 @@ Java:: + [source,java,indent=0,subs="verbatim,quotes",role="primary"] ---- - // Officer's Dictionary - - Inventor pupin = parser.parseExpression("officers['president']").getValue( - societyContext, Inventor.class); - - // evaluates to "Idvor" - String city = parser.parseExpression("officers['president'].placeOfBirth.city").getValue( - societyContext, String.class); - - // setting values - parser.parseExpression("officers['advisors'][0].placeOfBirth.country").setValue( - societyContext, "Croatia"); + // evaluates to "T" (8th letter of "Nikola Tesla") + String character = parser.parseExpression("members[0].name[7]") + .getValue(societyContext, String.class); ---- Kotlin:: + [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] ---- - // Officer's Dictionary - - val pupin = parser.parseExpression("officers['president']").getValue( - societyContext, Inventor::class.java) - - // evaluates to "Idvor" - val city = parser.parseExpression("officers['president'].placeOfBirth.city").getValue( - societyContext, String::class.java) - - // setting values - parser.parseExpression("officers['advisors'][0].placeOfBirth.country").setValue( - societyContext, "Croatia") + // evaluates to "T" (8th letter of "Nikola Tesla") + val character = parser.parseExpression("members[0].name[7]") + .getValue(societyContext, String::class.java) ---- ====== +[[expressions-indexing-maps]] +== Indexing into Maps +The contents of maps are obtained by specifying the key value within square brackets. In +the following example, because keys for the `officers` map are strings, we can specify +string literals such as `'president'`: + +[tabs] +====== +Java:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- + // Officer's Map + + // evaluates to Inventor("Pupin") + Inventor pupin = parser.parseExpression("officers['president']") + .getValue(societyContext, Inventor.class); + + // evaluates to "Idvor" + String city = parser.parseExpression("officers['president'].placeOfBirth.city") + .getValue(societyContext, String.class); + + String countryExpression = "officers['advisors'][0].placeOfBirth.country"; + + // setting values + parser.parseExpression(countryExpression) + .setValue(societyContext, "Croatia"); + + // evaluates to "Croatia" + String country = parser.parseExpression(countryExpression) + .getValue(societyContext, String.class); +---- + +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +---- + // Officer's Map + + // evaluates to Inventor("Pupin") + val pupin = parser.parseExpression("officers['president']") + .getValue(societyContext, Inventor::class.java) + + // evaluates to "Idvor" + val city = parser.parseExpression("officers['president'].placeOfBirth.city") + .getValue(societyContext, String::class.java) + + val countryExpression = "officers['advisors'][0].placeOfBirth.country" + + // setting values + parser.parseExpression(countryExpression) + .setValue(societyContext, "Croatia") + + // evaluates to "Croatia" + val country = parser.parseExpression(countryExpression) + .getValue(societyContext, String::class.java) +---- +====== + +[[expressions-indexing-objects]] +== Indexing into Objects + +A property of an object can be obtained by specifying the name of the property within +square brackets. This is analogous to accessing the value of a map based on its key. The +following example demonstrates how to _index_ into an object to retrieve a specific +property. + +[tabs] +====== +Java:: ++ +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +---- + // Create an inventor to use as the root context object. + Inventor tesla = new Inventor("Nikola Tesla"); + + // evaluates to "Nikola Tesla" + String name = parser.parseExpression("#root['name']") + .getValue(context, tesla, String.class); +---- + +Kotlin:: ++ +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +---- + // Create an inventor to use as the root context object. + val tesla = Inventor("Nikola Tesla") + + // evaluates to "Nikola Tesla" + val name = parser.parseExpression("#root['name']") + .getValue(context, tesla, String::class.java) +---- +====== diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/SpelDocumentationTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/SpelDocumentationTests.java index c223ab2570..07c9bd3b79 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/SpelDocumentationTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/SpelDocumentationTests.java @@ -161,60 +161,99 @@ class SpelDocumentationTests extends AbstractExpressionTests { class PropertiesArraysListsMapsAndIndexers { @Test - void propertyAccess() { + void propertyNavigation() { EvaluationContext context = TestScenarioCreator.getTestEvaluationContext(); + + // evaluates to 1856 int year = (Integer) parser.parseExpression("Birthdate.Year + 1900").getValue(context); // 1856 assertThat(year).isEqualTo(1856); + // evaluates to "Smiljan" String city = (String) parser.parseExpression("placeOfBirth.City").getValue(context); assertThat(city).isEqualTo("Smiljan"); } @Test - void propertyNavigation() { + void indexingIntoArraysAndCollections() { ExpressionParser parser = new SpelExpressionParser(); StandardEvaluationContext teslaContext = TestScenarioCreator.getTestEvaluationContext(); + StandardEvaluationContext societyContext = new StandardEvaluationContext(); + societyContext.setRootObject(new IEEE()); // Inventions Array + // evaluates to "Induction motor" String invention = parser.parseExpression("inventions[3]").getValue(teslaContext, String.class); assertThat(invention).isEqualTo("Induction motor"); // Members List - StandardEvaluationContext societyContext = new StandardEvaluationContext(); - societyContext.setRootObject(new IEEE()); // evaluates to "Nikola Tesla" String name = parser.parseExpression("members[0].Name").getValue(societyContext, String.class); assertThat(name).isEqualTo("Nikola Tesla"); - // List and Array navigation + // List and Array Indexing + // evaluates to "Wireless communication" invention = parser.parseExpression("members[0].Inventions[6]").getValue(societyContext, String.class); assertThat(invention).isEqualTo("Wireless communication"); } @Test - void maps() { + void indexingIntoStrings() { + ExpressionParser parser = new SpelExpressionParser(); StandardEvaluationContext societyContext = new StandardEvaluationContext(); societyContext.setRootObject(new IEEE()); - // Officer's map - Inventor pupin = parser.parseExpression("officers['president']").getValue(societyContext, Inventor.class); + + // evaluates to "T" (8th letter of "Nikola Tesla") + String character = parser.parseExpression("members[0].name[7]") + .getValue(societyContext, String.class); + assertThat(character).isEqualTo("T"); + } + + @Test + void indexingIntoMaps() { + StandardEvaluationContext societyContext = new StandardEvaluationContext(); + societyContext.setRootObject(new IEEE()); + + // Officer's Map + + // evaluates to Inventor("Pupin") + Inventor pupin = parser.parseExpression("officers['president']") + .getValue(societyContext, Inventor.class); assertThat(pupin).isNotNull(); + assertThat(pupin.getName()).isEqualTo("Pupin"); // evaluates to "Idvor" - String city = parser.parseExpression("officers['president'].PlaceOfBirth.city").getValue(societyContext, String.class); - assertThat(city).isNotNull(); + String city = parser.parseExpression("officers['president'].placeOfBirth.city") + .getValue(societyContext, String.class); + assertThat(city).isEqualTo("Idvor"); + + String countryExpression = "officers['advisors'][0].placeOfBirth.Country"; // setting values - Inventor i = parser.parseExpression("officers['advisors'][0]").getValue(societyContext, Inventor.class); - assertThat(i.getName()).isEqualTo("Nikola Tesla"); + parser.parseExpression(countryExpression) + .setValue(societyContext, "Croatia"); - parser.parseExpression("officers['advisors'][0].PlaceOfBirth.Country").setValue(societyContext, "Croatia"); - - Inventor i2 = parser.parseExpression("reverse[0]['advisors'][0]").getValue(societyContext, Inventor.class); - assertThat(i2.getName()).isEqualTo("Nikola Tesla"); + // evaluates to "Croatia" + String country = parser.parseExpression(countryExpression) + .getValue(societyContext, String.class); + assertThat(country).isEqualTo("Croatia"); } + + @Test + void indexingIntoObjects() { + ExpressionParser parser = new SpelExpressionParser(); + + // Create an inventor to use as the root context object. + Inventor tesla = new Inventor("Nikola Tesla"); + + // evaluates to "Nikola Tesla" + String name = parser.parseExpression("#root['name']") + .getValue(context, tesla, String.class); + assertThat(name).isEqualTo("Nikola Tesla"); + } + } @Nested