1
1
/*
2
- * Copyright 2002-2021 the original author or authors.
2
+ * Copyright 2002-2024 the original author or authors.
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
16
16
17
17
package org .springframework .test .util ;
18
18
19
+ import java .lang .reflect .Type ;
19
20
import java .util .List ;
20
21
import java .util .Map ;
22
+ import java .util .function .Function ;
21
23
24
+ import com .jayway .jsonpath .Configuration ;
25
+ import com .jayway .jsonpath .DocumentContext ;
22
26
import com .jayway .jsonpath .JsonPath ;
27
+ import com .jayway .jsonpath .TypeRef ;
28
+ import com .jayway .jsonpath .spi .mapper .MappingProvider ;
23
29
import org .hamcrest .CoreMatchers ;
24
30
import org .hamcrest .Matcher ;
25
31
import org .hamcrest .MatcherAssert ;
26
32
33
+ import org .springframework .core .ParameterizedTypeReference ;
27
34
import org .springframework .lang .Nullable ;
28
35
import org .springframework .util .Assert ;
29
36
import org .springframework .util .ClassUtils ;
40
47
* @author Juergen Hoeller
41
48
* @author Craig Andrews
42
49
* @author Sam Brannen
50
+ * @author Stephane Nicoll
43
51
* @since 3.2
44
52
*/
45
53
public class JsonPathExpectationsHelper {
@@ -48,17 +56,42 @@ public class JsonPathExpectationsHelper {
48
56
49
57
private final JsonPath jsonPath ;
50
58
59
+ private final Configuration configuration ;
60
+
61
+ /**
62
+ * Construct a new {@code JsonPathExpectationsHelper}.
63
+ * @param expression the {@link JsonPath} expression; never {@code null} or empty
64
+ * @param configuration the {@link Configuration} to use or {@code null} to use the
65
+ * {@linkplain Configuration#defaultConfiguration() default configuration}
66
+ * @since 6.2
67
+ */
68
+ public JsonPathExpectationsHelper (String expression , @ Nullable Configuration configuration ) {
69
+ Assert .hasText (expression , "expression must not be null or empty" );
70
+ this .expression = expression ;
71
+ this .jsonPath = JsonPath .compile (this .expression );
72
+ this .configuration = (configuration != null ) ? configuration : Configuration .defaultConfiguration ();
73
+ }
74
+
75
+ /**
76
+ * Construct a new {@code JsonPathExpectationsHelper} using the
77
+ * {@linkplain Configuration#defaultConfiguration() default configuration}.
78
+ * @param expression the {@link JsonPath} expression; never {@code null} or empty
79
+ * @since 6.2
80
+ */
81
+ public JsonPathExpectationsHelper (String expression ) {
82
+ this (expression , (Configuration ) null );
83
+ }
51
84
52
85
/**
53
86
* Construct a new {@code JsonPathExpectationsHelper}.
54
87
* @param expression the {@link JsonPath} expression; never {@code null} or empty
55
88
* @param args arguments to parameterize the {@code JsonPath} expression with,
56
89
* using formatting specifiers defined in {@link String#format(String, Object...)}
90
+ * @deprecated in favor of calling {@link String#formatted(Object...)} upfront
57
91
*/
92
+ @ Deprecated (since = "6.2" , forRemoval = true )
58
93
public JsonPathExpectationsHelper (String expression , Object ... args ) {
59
- Assert .hasText (expression , "expression must not be null or empty" );
60
- this .expression = String .format (expression , args );
61
- this .jsonPath = JsonPath .compile (this .expression );
94
+ this (expression .formatted (args ), (Configuration ) null );
62
95
}
63
96
64
97
@@ -83,9 +116,25 @@ public <T> void assertValue(String content, Matcher<? super T> matcher) {
83
116
* @param targetType the expected type of the resulting value
84
117
* @since 4.3.3
85
118
*/
86
- @ SuppressWarnings ("unchecked" )
87
119
public <T > void assertValue (String content , Matcher <? super T > matcher , Class <T > targetType ) {
88
- T value = (T ) evaluateJsonPath (content , targetType );
120
+ T value = evaluateJsonPath (content , targetType );
121
+ MatcherAssert .assertThat ("JSON path \" " + this .expression + "\" " , value , matcher );
122
+ }
123
+
124
+ /**
125
+ * An overloaded variant of {@link #assertValue(String, Matcher)} that also
126
+ * accepts a target type for the resulting value that allows generic types
127
+ * to be defined.
128
+ * <p>This must be used with a {@link Configuration} that defines a more
129
+ * elaborate {@link MappingProvider} as the default one cannot handle
130
+ * generic types.
131
+ * @param content the JSON content
132
+ * @param matcher the matcher with which to assert the result
133
+ * @param targetType the expected type of the resulting value
134
+ * @since 6.2
135
+ */
136
+ public <T > void assertValue (String content , Matcher <? super T > matcher , ParameterizedTypeReference <T > targetType ) {
137
+ T value = evaluateJsonPath (content , targetType );
89
138
MatcherAssert .assertThat ("JSON path \" " + this .expression + "\" " , value , matcher );
90
139
}
91
140
@@ -296,7 +345,7 @@ private String failureReason(String expectedDescription, @Nullable Object value)
296
345
@ Nullable
297
346
public Object evaluateJsonPath (String content ) {
298
347
try {
299
- return this .jsonPath .read (content );
348
+ return this .jsonPath .read (content , this . configuration );
300
349
}
301
350
catch (Throwable ex ) {
302
351
throw new AssertionError ("No value at JSON path \" " + this .expression + "\" " , ex );
@@ -306,19 +355,32 @@ public Object evaluateJsonPath(String content) {
306
355
/**
307
356
* Variant of {@link #evaluateJsonPath(String)} with a target type.
308
357
* <p>This can be useful for matching numbers reliably for example coercing an
309
- * integer into a double.
358
+ * integer into a double or when the configured {@link MappingProvider} can
359
+ * handle more complex object structures.
310
360
* @param content the content to evaluate against
361
+ * @param targetType the requested target type
311
362
* @return the result of the evaluation
312
363
* @throws AssertionError if the evaluation fails
313
364
*/
314
- public Object evaluateJsonPath (String content , Class <?> targetType ) {
315
- try {
316
- return JsonPath .parse (content ).read (this .expression , targetType );
317
- }
318
- catch (Throwable ex ) {
319
- String message = "No value at JSON path \" " + this .expression + "\" " ;
320
- throw new AssertionError (message , ex );
321
- }
365
+ public <T > T evaluateJsonPath (String content , Class <T > targetType ) {
366
+ return evaluateExpression (content , context -> context .read (this .expression , targetType ));
367
+ }
368
+
369
+ /**
370
+ * Variant of {@link #evaluateJsonPath(String)} with a target type that has
371
+ * generics.
372
+ * <p>This must be used with a {@link Configuration} that defines a more
373
+ * elaborate {@link MappingProvider} as the default one cannot handle
374
+ * generic types.
375
+ * @param content the content to evaluate against
376
+ * @param targetType the requested target type
377
+ * @return the result of the evaluation
378
+ * @throws AssertionError if the evaluation fails
379
+ * @since 6.2
380
+ */
381
+ public <T > T evaluateJsonPath (String content , ParameterizedTypeReference <T > targetType ) {
382
+ return evaluateExpression (content , context ->
383
+ context .read (this .expression , new TypeRefAdapter <>(targetType )));
322
384
}
323
385
324
386
@ Nullable
@@ -336,4 +398,35 @@ private boolean pathIsIndefinite() {
336
398
return !this .jsonPath .isDefinite ();
337
399
}
338
400
401
+
402
+ private <T > T evaluateExpression (String content , Function <DocumentContext , T > action ) {
403
+ try {
404
+ DocumentContext context = JsonPath .parse (content , this .configuration );
405
+ return action .apply (context );
406
+ }
407
+ catch (Throwable ex ) {
408
+ String message = "Failed to evaluate JSON path \" " + this .expression + "\" " ;
409
+ throw new AssertionError (message , ex );
410
+ }
411
+ }
412
+
413
+
414
+ /**
415
+ * Adapt JSONPath {@link TypeRef} to {@link ParameterizedTypeReference}.
416
+ */
417
+ private static final class TypeRefAdapter <T > extends TypeRef <T > {
418
+
419
+ private final Type type ;
420
+
421
+ TypeRefAdapter (ParameterizedTypeReference <T > typeReference ) {
422
+ this .type = typeReference .getType ();
423
+ }
424
+
425
+ @ Override
426
+ public Type getType () {
427
+ return this .type ;
428
+ }
429
+
430
+ }
431
+
339
432
}
0 commit comments