Document null-safe collection selection/projection support in SpEL
Closes gh-32208
This commit is contained in:
parent
347d085020
commit
4a5dc7c1b0
|
|
@ -34,5 +34,12 @@ evaluated against each entry in the map (represented as a Java `Map.Entry`). The
|
|||
of a projection across a map is a list that consists of the evaluation of the projection
|
||||
expression against each map entry.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
The Spring Expression Language also supports safe navigation for collection projection.
|
||||
|
||||
See
|
||||
xref:core/expressions/language-ref/operator-safe-navigation.adoc#expressions-operator-safe-navigation-selection-and-projection[Safe Collection Selection and Projection]
|
||||
for details.
|
||||
====
|
||||
|
||||
|
|
|
|||
|
|
@ -59,5 +59,12 @@ the last element. To obtain the first element matching the selection expression,
|
|||
syntax is `.^[selectionExpression]`. To obtain the last element matching the selection
|
||||
expression, the syntax is `.$[selectionExpression]`.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
The Spring Expression Language also supports safe navigation for collection selection.
|
||||
|
||||
See
|
||||
xref:core/expressions/language-ref/operator-safe-navigation.adoc#expressions-operator-safe-navigation-selection-and-projection[Safe Collection Selection and Projection]
|
||||
for details.
|
||||
====
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@ language. Typically, when you have a reference to an object, you might need to v
|
|||
that it is not `null` before accessing methods or properties of the object. To avoid
|
||||
this, the safe navigation operator returns `null` instead of throwing an exception.
|
||||
|
||||
[[expressions-operator-safe-navigation-property-access]]
|
||||
== Safe Property and Method Access
|
||||
|
||||
The following example shows how to use the safe navigation operator for property access
|
||||
(`?.`).
|
||||
|
||||
|
|
@ -59,3 +62,224 @@ Kotlin::
|
|||
<2> Use safe navigation operator on null `placeOfBirth` property
|
||||
======
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
The safe navigation operator also applies to method invocations on an object.
|
||||
|
||||
For example, the expression `#calculator?.max(4, 2)` evaluates to `null` if the
|
||||
`#calculator` variable has not been configured in the context. Otherwise, the
|
||||
`max(int, int)` method will be invoked on the `#calculator`.
|
||||
====
|
||||
|
||||
|
||||
[[expressions-operator-safe-navigation-selection-and-projection]]
|
||||
== Safe Collection Selection and Projection
|
||||
|
||||
The Spring Expression Language supports safe navigation for
|
||||
xref:core/expressions/language-ref/collection-selection.adoc[collection selection] and
|
||||
xref:core/expressions/language-ref/collection-projection.adoc[collection projection] via
|
||||
the following operators.
|
||||
|
||||
* null-safe selection: `?.?`
|
||||
* null-safe select first: `?.^`
|
||||
* null-safe select last: `?.$`
|
||||
* null-safe projection: `?.!`
|
||||
|
||||
The following example shows how to use the safe navigation operator for collection
|
||||
selection (`?.?`).
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
IEEE society = new IEEE();
|
||||
StandardEvaluationContext context = new StandardEvaluationContext(society);
|
||||
String expression = "members?.?[nationality == 'Serbian']"; // <1>
|
||||
|
||||
// evaluates to [Inventor("Nikola Tesla")]
|
||||
List<Inventor> list = (List<Inventor>) parser.parseExpression(expression)
|
||||
.getValue(context);
|
||||
|
||||
society.members = null;
|
||||
|
||||
// evaluates to null - does not throw a NullPointerException
|
||||
list = (List<Inventor>) parser.parseExpression(expression)
|
||||
.getValue(context);
|
||||
----
|
||||
<1> Use null-safe selection operator on potentially null `members` list
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
val society = IEEE()
|
||||
val context = StandardEvaluationContext(society)
|
||||
val expression = "members?.?[nationality == 'Serbian']" // <1>
|
||||
|
||||
// evaluates to [Inventor("Nikola Tesla")]
|
||||
var list = parser.parseExpression(expression)
|
||||
.getValue(context) as List<Inventor>
|
||||
|
||||
society.members = null
|
||||
|
||||
// evaluates to null - does not throw a NullPointerException
|
||||
list = parser.parseExpression(expression)
|
||||
.getValue(context) as List<Inventor>
|
||||
----
|
||||
<1> Use null-safe selection operator on potentially null `members` list
|
||||
======
|
||||
|
||||
The following example shows how to use the "null-safe select first" operator for
|
||||
collections (`?.^`).
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
IEEE society = new IEEE();
|
||||
StandardEvaluationContext context = new StandardEvaluationContext(society);
|
||||
String expression =
|
||||
"members?.^[nationality == 'Serbian' || nationality == 'Idvor']"; // <1>
|
||||
|
||||
// evaluates to Inventor("Nikola Tesla")
|
||||
Inventor inventor = parser.parseExpression(expression)
|
||||
.getValue(context, Inventor.class);
|
||||
|
||||
society.members = null;
|
||||
|
||||
// evaluates to null - does not throw a NullPointerException
|
||||
inventor = parser.parseExpression(expression)
|
||||
.getValue(context, Inventor.class);
|
||||
----
|
||||
<1> Use "null-safe select first" operator on potentially null `members` list
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
val society = IEEE()
|
||||
val context = StandardEvaluationContext(society)
|
||||
val expression =
|
||||
"members?.^[nationality == 'Serbian' || nationality == 'Idvor']" // <1>
|
||||
|
||||
// evaluates to Inventor("Nikola Tesla")
|
||||
var inventor = parser.parseExpression(expression)
|
||||
.getValue(context, Inventor::class.java)
|
||||
|
||||
society.members = null
|
||||
|
||||
// evaluates to null - does not throw a NullPointerException
|
||||
inventor = parser.parseExpression(expression)
|
||||
.getValue(context, Inventor::class.java)
|
||||
----
|
||||
<1> Use "null-safe select first" operator on potentially null `members` list
|
||||
======
|
||||
|
||||
|
||||
The following example shows how to use the "null-safe select last" operator for
|
||||
collections (`?.$`).
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
IEEE society = new IEEE();
|
||||
StandardEvaluationContext context = new StandardEvaluationContext(society);
|
||||
String expression =
|
||||
"members?.$[nationality == 'Serbian' || nationality == 'Idvor']"; // <1>
|
||||
|
||||
// evaluates to Inventor("Pupin")
|
||||
Inventor inventor = parser.parseExpression(expression)
|
||||
.getValue(context, Inventor.class);
|
||||
|
||||
society.members = null;
|
||||
|
||||
// evaluates to null - does not throw a NullPointerException
|
||||
inventor = parser.parseExpression(expression)
|
||||
.getValue(context, Inventor.class);
|
||||
----
|
||||
<1> Use "null-safe select last" operator on potentially null `members` list
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
val society = IEEE()
|
||||
val context = StandardEvaluationContext(society)
|
||||
val expression =
|
||||
"members?.$[nationality == 'Serbian' || nationality == 'Idvor']" // <1>
|
||||
|
||||
// evaluates to Inventor("Pupin")
|
||||
var inventor = parser.parseExpression(expression)
|
||||
.getValue(context, Inventor::class.java)
|
||||
|
||||
society.members = null
|
||||
|
||||
// evaluates to null - does not throw a NullPointerException
|
||||
inventor = parser.parseExpression(expression)
|
||||
.getValue(context, Inventor::class.java)
|
||||
----
|
||||
<1> Use "null-safe select last" operator on potentially null `members` list
|
||||
======
|
||||
|
||||
The following example shows how to use the safe navigation operator for collection
|
||||
projection (`?.!`).
|
||||
|
||||
[tabs]
|
||||
======
|
||||
Java::
|
||||
+
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
----
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
IEEE society = new IEEE();
|
||||
StandardEvaluationContext context = new StandardEvaluationContext(society);
|
||||
|
||||
// evaluates to ["Smiljan", "Idvor"]
|
||||
List placesOfBirth = parser.parseExpression("members?.![placeOfBirth.city]") // <1>
|
||||
.getValue(context, List.class);
|
||||
|
||||
society.members = null;
|
||||
|
||||
// evaluates to null - does not throw a NullPointerException
|
||||
placesOfBirth = parser.parseExpression("members?.![placeOfBirth.city]") // <2>
|
||||
.getValue(context, List.class);
|
||||
----
|
||||
<1> Use null-safe projection operator on non-null `members` list
|
||||
<2> Use null-safe projection operator on null `members` list
|
||||
|
||||
Kotlin::
|
||||
+
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
----
|
||||
val parser = SpelExpressionParser()
|
||||
val society = IEEE()
|
||||
val context = StandardEvaluationContext(society)
|
||||
|
||||
// evaluates to ["Smiljan", "Idvor"]
|
||||
var placesOfBirth = parser.parseExpression("members?.![placeOfBirth.city]") // <1>
|
||||
.getValue(context, List::class.java)
|
||||
|
||||
society.members = null
|
||||
|
||||
// evaluates to null - does not throw a NullPointerException
|
||||
placesOfBirth = parser.parseExpression("members?.![placeOfBirth.city]") // <2>
|
||||
.getValue(context, List::class.java)
|
||||
----
|
||||
<1> Use null-safe projection operator on non-null `members` list
|
||||
<2> Use null-safe projection operator on null `members` list
|
||||
======
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -648,6 +648,85 @@ class SpelDocumentationTests extends AbstractExpressionTests {
|
|||
.getValue(context, tesla, String.class);
|
||||
assertThat(city).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void nullSafeSelection() {
|
||||
IEEE society = new IEEE();
|
||||
StandardEvaluationContext context = new StandardEvaluationContext(society);
|
||||
String expression = "members?.?[nationality == 'Serbian']"; // <1>
|
||||
|
||||
// evaluates to [Inventor("Nikola Tesla")]
|
||||
List<Inventor> list = (List<Inventor>) parser.parseExpression(expression)
|
||||
.getValue(context);
|
||||
assertThat(list).map(Inventor::getName).containsOnly("Nikola Tesla");
|
||||
|
||||
society.members = null;
|
||||
|
||||
// evaluates to null - does not throw a NullPointerException
|
||||
list = (List<Inventor>) parser.parseExpression(expression)
|
||||
.getValue(context);
|
||||
assertThat(list).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void nullSafeSelectFirst() {
|
||||
IEEE society = new IEEE();
|
||||
StandardEvaluationContext context = new StandardEvaluationContext(society);
|
||||
String expression = "members?.^[nationality == 'Serbian' || nationality == 'Idvor']"; // <1>
|
||||
|
||||
// evaluates to Inventor("Nikola Tesla")
|
||||
Inventor inventor = parser.parseExpression(expression)
|
||||
.getValue(context, Inventor.class);
|
||||
assertThat(inventor).extracting(Inventor::getName).isEqualTo("Nikola Tesla");
|
||||
|
||||
society.members = null;
|
||||
|
||||
// evaluates to null - does not throw a NullPointerException
|
||||
inventor = parser.parseExpression(expression)
|
||||
.getValue(context, Inventor.class);
|
||||
assertThat(inventor).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void nullSafeSelectLast() {
|
||||
IEEE society = new IEEE();
|
||||
StandardEvaluationContext context = new StandardEvaluationContext(society);
|
||||
String expression = "members?.$[nationality == 'Serbian' || nationality == 'Idvor']"; // <1>
|
||||
|
||||
// evaluates to Inventor("Pupin")
|
||||
Inventor inventor = parser.parseExpression(expression)
|
||||
.getValue(context, Inventor.class);
|
||||
assertThat(inventor).extracting(Inventor::getName).isEqualTo("Pupin");
|
||||
|
||||
society.members = null;
|
||||
|
||||
// evaluates to null - does not throw a NullPointerException
|
||||
inventor = parser.parseExpression(expression)
|
||||
.getValue(context, Inventor.class);
|
||||
assertThat(inventor).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void nullSafeProjection() {
|
||||
IEEE society = new IEEE();
|
||||
StandardEvaluationContext context = new StandardEvaluationContext(society);
|
||||
|
||||
// evaluates to ["Smiljan", "Idvor"]
|
||||
List placesOfBirth = parser.parseExpression("members?.![placeOfBirth.city]") // <1>
|
||||
.getValue(context, List.class);
|
||||
assertThat(placesOfBirth).containsExactly("Smiljan", "Idvor");
|
||||
|
||||
society.members = null;
|
||||
|
||||
// evaluates to null - does not throw a NullPointerException
|
||||
placesOfBirth = parser.parseExpression("members?.![placeOfBirth.city]") // <2>
|
||||
.getValue(context, List.class);
|
||||
assertThat(placesOfBirth).isNull();
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
|
|
|
|||
Loading…
Reference in New Issue