GH-9383: Introduce JsonIndexAccessor
Fixes: #9383 Issue link: https://github.com/spring-projects/spring-integration/issues/9383 * Polish `JsonPropertyAccessor[Tests]` * Introduce `JsonIndexAccessor` This commit introduces a `JsonIndexAccessor` as a complement to the existing `JsonPropertyAccessor`. When a `JsonIndexAccessor` is registered with the SpEL `EvaluationContext`, JSON arrays can be consistently indexed via integer literals (e.g.,[1]) instead of string literals representing integers (e.g., ['1']).
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2013-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.json;
|
||||
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
|
||||
import org.springframework.expression.AccessException;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.IndexAccessor;
|
||||
import org.springframework.expression.TypedValue;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* A SpEL {@link IndexAccessor} that knows how to read indexes from JSON arrays, using
|
||||
* Jackson's {@link ArrayNode} API.
|
||||
*
|
||||
* <p>Supports indexes supplied as an integer literal — for example, {@code myJsonArray[1]}.
|
||||
* Also supports negative indexes — for example, {@code myJsonArray[-1]} which equates
|
||||
* to {@code myJsonArray[myJsonArray.length - 1]}. Furthermore, {@code null} is returned for
|
||||
* any index that is out of bounds (see {@link ArrayNode#get(int)} for details).
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 6.4
|
||||
* @see JsonPropertyAccessor
|
||||
*/
|
||||
public class JsonIndexAccessor implements IndexAccessor {
|
||||
|
||||
private static final Class<?>[] SUPPORTED_CLASSES = { ArrayNode.class };
|
||||
|
||||
@Override
|
||||
public Class<?>[] getSpecificTargetClasses() {
|
||||
return SUPPORTED_CLASSES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRead(EvaluationContext context, Object target, Object index) {
|
||||
return (target instanceof ArrayNode && index instanceof Integer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypedValue read(EvaluationContext context, Object target, Object index) throws AccessException {
|
||||
ArrayNode arrayNode = (ArrayNode) target;
|
||||
Integer intIndex = (Integer) index;
|
||||
if (intIndex < 0) {
|
||||
// negative index: get from the end of array, for compatibility with JsonPropertyAccessor.ArrayNodeAsList.
|
||||
intIndex = arrayNode.size() + intIndex;
|
||||
}
|
||||
return JsonPropertyAccessor.typedValue(arrayNode.get(intIndex));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canWrite(EvaluationContext context, Object target, Object index) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(EvaluationContext context, Object target, Object index, @Nullable Object newValue) {
|
||||
throw new UnsupportedOperationException("Write is not supported");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2023 the original author or authors.
|
||||
* Copyright 2013-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -24,6 +24,7 @@ import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.NullNode;
|
||||
|
||||
import org.springframework.expression.AccessException;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
@@ -35,7 +36,7 @@ import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A SpEL {@link PropertyAccessor} that knows how to read properties from JSON objects.
|
||||
* Uses Jackson {@link JsonNode} API for nested properties access.
|
||||
* <p>Uses Jackson {@link JsonNode} API for nested properties access.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
* @author Artem Bilan
|
||||
@@ -43,8 +44,10 @@ import org.springframework.util.StringUtils;
|
||||
* @author Gary Russell
|
||||
* @author Pierre Lakreb
|
||||
* @author Vladislav Fefelov
|
||||
* @author Sam Brannen
|
||||
*
|
||||
* @since 3.0
|
||||
* @see JsonIndexAccessor
|
||||
*/
|
||||
public class JsonPropertyAccessor implements PropertyAccessor {
|
||||
|
||||
@@ -80,23 +83,22 @@ public class JsonPropertyAccessor implements PropertyAccessor {
|
||||
// Cannot parse - treat as not a JSON
|
||||
return false;
|
||||
}
|
||||
Integer index = maybeIndex(name);
|
||||
if (node instanceof ArrayNode) {
|
||||
return index != null;
|
||||
return maybeIndex(name) != null;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private JsonNode asJson(Object target) throws AccessException {
|
||||
if (target instanceof JsonNode) {
|
||||
return (JsonNode) target;
|
||||
if (target instanceof JsonNode jsonNode) {
|
||||
return jsonNode;
|
||||
}
|
||||
else if (target instanceof JsonNodeWrapper) {
|
||||
return ((JsonNodeWrapper<?>) target).getRealNode();
|
||||
else if (target instanceof JsonNodeWrapper<?> jsonNodeWrapper) {
|
||||
return jsonNodeWrapper.getRealNode();
|
||||
}
|
||||
else if (target instanceof String) {
|
||||
else if (target instanceof String content) {
|
||||
try {
|
||||
return this.objectMapper.readTree((String) target);
|
||||
return this.objectMapper.readTree(content);
|
||||
}
|
||||
catch (JsonProcessingException e) {
|
||||
throw new AccessException("Exception while trying to deserialize String", e);
|
||||
@@ -160,8 +162,8 @@ public class JsonPropertyAccessor implements PropertyAccessor {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static TypedValue typedValue(JsonNode json) throws AccessException {
|
||||
if (json == null) {
|
||||
static TypedValue typedValue(JsonNode json) throws AccessException {
|
||||
if (json == null || json instanceof NullNode) {
|
||||
return TypedValue.NULL;
|
||||
}
|
||||
else if (json.isValueNode()) {
|
||||
@@ -199,8 +201,8 @@ public class JsonPropertyAccessor implements PropertyAccessor {
|
||||
if (json == null) {
|
||||
return null;
|
||||
}
|
||||
else if (json instanceof ArrayNode) {
|
||||
return new ArrayNodeAsList((ArrayNode) json);
|
||||
else if (json instanceof ArrayNode arrayNode) {
|
||||
return new ArrayNodeAsList(arrayNode);
|
||||
}
|
||||
else if (json.isValueNode()) {
|
||||
return getValue(json);
|
||||
@@ -212,8 +214,6 @@ public class JsonPropertyAccessor implements PropertyAccessor {
|
||||
|
||||
interface JsonNodeWrapper<T> extends Comparable<T> {
|
||||
|
||||
String toString();
|
||||
|
||||
JsonNode getRealNode();
|
||||
|
||||
}
|
||||
@@ -309,10 +309,8 @@ public class JsonPropertyAccessor implements PropertyAccessor {
|
||||
|
||||
@Override
|
||||
public int compareTo(Object o) {
|
||||
if (o instanceof JsonNodeWrapper<?>) {
|
||||
return this.delegate.equals(((JsonNodeWrapper<?>) o).getRealNode()) ? 0 : 1;
|
||||
}
|
||||
return this.delegate.equals(o) ? 0 : 1;
|
||||
Object that = (o instanceof JsonNodeWrapper<?> wrapper ? wrapper.getRealNode() : o);
|
||||
return this.delegate.equals(that) ? 0 : 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,366 @@
|
||||
/*
|
||||
* Copyright 2013-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.json;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.fasterxml.jackson.databind.node.TextNode;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.spel.SpelEvaluationException;
|
||||
import org.springframework.expression.spel.SpelMessage;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.expression.spel.support.StandardTypeConverter;
|
||||
import org.springframework.integration.json.JsonPropertyAccessor.ArrayNodeAsList;
|
||||
import org.springframework.integration.json.JsonPropertyAccessor.ComparableJsonNode;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
/**
|
||||
* Abstract base class for tests involving {@link JsonPropertyAccessor} and {@link JsonIndexAccessor}.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
* @author Artem Bilan
|
||||
* @author Paul Martin
|
||||
* @author Pierre Lakreb
|
||||
* @author Sam Brannen
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
abstract class AbstractJsonAccessorTests {
|
||||
|
||||
protected final SpelExpressionParser parser = new SpelExpressionParser();
|
||||
|
||||
protected final ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
protected final StandardEvaluationContext context = new StandardEvaluationContext();
|
||||
|
||||
@BeforeEach
|
||||
void setUpEvaluationContext() {
|
||||
DefaultConversionService conversionService = new DefaultConversionService();
|
||||
conversionService.addConverter(new JsonNodeWrapperToJsonNodeConverter());
|
||||
context.setTypeConverter(new StandardTypeConverter(conversionService));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for JSON accessors that use an instance of {@link JsonNode} as the
|
||||
* root context object.
|
||||
*/
|
||||
@Nested
|
||||
class JsonNodeTests {
|
||||
|
||||
@Test
|
||||
void textNode() throws Exception {
|
||||
TextNode json = (TextNode) mapper.readTree("\"foo\"");
|
||||
String result = evaluate(json, "#root", String.class);
|
||||
assertThat(result).isEqualTo("\"foo\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullProperty() throws Exception {
|
||||
JsonNode json = mapper.readTree("{\"foo\": null}");
|
||||
assertThat(evaluate(json, "foo", String.class)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void missingProperty() throws Exception {
|
||||
JsonNode json = mapper.readTree(FOO_BAR_JSON);
|
||||
assertThat(evaluate(json, "fizz", String.class)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void propertyLookup() throws Exception {
|
||||
JsonNode json1 = mapper.readTree(FOO_BAR_JSON);
|
||||
String value1 = evaluate(json1, "foo", String.class);
|
||||
assertThat(value1).isEqualTo("bar");
|
||||
JsonNode json2 = mapper.readTree(FOO_BAR_JSON);
|
||||
String value2 = evaluate(json2, "foo", String.class);
|
||||
assertThat(value1).isEqualTo(value2).hasSameHashCodeAs(value2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void arrayLookupWithIntegerIndexAndExplicitWrapping() throws Exception {
|
||||
ArrayNode json = (ArrayNode) mapper.readTree("[3, 4, 5]");
|
||||
// Have to wrap the root array because ArrayNode itself is not a List
|
||||
Integer actual = evaluate(JsonPropertyAccessor.wrap(json), "[1]", Integer.class);
|
||||
assertThat(actual).isEqualTo(4);
|
||||
}
|
||||
|
||||
@Test
|
||||
void arrayLookupWithIntegerIndexForNullValueAndExplicitWrapping() throws Exception {
|
||||
ArrayNode json = (ArrayNode) mapper.readTree("[3, null, 5]");
|
||||
// Have to wrap the root array because ArrayNode itself is not a List
|
||||
Integer actual = evaluate(JsonPropertyAccessor.wrap(json), "[1]", Integer.class);
|
||||
assertThat(actual).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void arrayLookupWithNegativeIntegerIndex() throws Exception {
|
||||
JsonNode json = mapper.readTree("{\"foo\": [3, 4, 5]}");
|
||||
// ArrayNodeAsList allows one to index into a JSON array via a negative index.
|
||||
assertThat(evaluate(json, "foo[-1]", Integer.class)).isEqualTo(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
void arrayLookupWithNegativeIntegerIndexGreaterThanArrayLength() throws Exception {
|
||||
JsonNode json = mapper.readTree("{\"foo\": [3, 4, 5]}");
|
||||
// Although ArrayNodeAsList allows one to index into a JSON array via a negative
|
||||
// index, if the result of (array.length - index) is still negative, Jackson's
|
||||
// ArrayNode.get() method returns null instead of throwing an IndexOutOfBoundsException.
|
||||
assertThat(evaluate(json, "foo[-99]", Integer.class)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void arrayLookupWithNegativeIntegerIndexForNullValue() throws Exception {
|
||||
JsonNode json = mapper.readTree("{\"foo\": [3, 4, null]}");
|
||||
// ArrayNodeAsList allows one to index into a JSON array via a negative index.
|
||||
assertThat(evaluate(json, "foo[-1]", Integer.class)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void arrayLookupWithIntegerIndexOutOfBounds() throws Exception {
|
||||
JsonNode json = mapper.readTree("{\"foo\": [3, 4, 5]}");
|
||||
assertThatExceptionOfType(SpelEvaluationException.class)
|
||||
.isThrownBy(() -> evaluate(json, "foo[3]", Object.class))
|
||||
.satisfies(ex -> assertThat(ex.getMessageCode()).isEqualTo(SpelMessage.COLLECTION_INDEX_OUT_OF_BOUNDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
void arrayLookupWithStringIndex() throws Exception {
|
||||
JsonNode json = mapper.readTree("[3, 4, 5]");
|
||||
Integer actual = evaluate(json, "['1']", Integer.class);
|
||||
assertThat(actual).isEqualTo(4);
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedArrayLookupWithIntegerIndexAndExplicitWrapping() throws Exception {
|
||||
ArrayNode json = (ArrayNode) mapper.readTree("[[3], [4, 5], []]");
|
||||
// JsonNode actual = evaluate(json, "1.1", JsonNode.class); // Does not work
|
||||
Object actual = evaluate(JsonPropertyAccessor.wrap(json), "[1][1]", Object.class);
|
||||
assertThat(actual).isEqualTo(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedArrayLookupWithStringIndex() throws Exception {
|
||||
JsonNode json = mapper.readTree("[[3], [4, 5], []]");
|
||||
Integer actual = evaluate(json, "['1']['1']", Integer.class);
|
||||
assertThat(actual).isEqualTo(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void nestedArrayLookupWithStringIndexAndThenIntegerIndex() throws Exception {
|
||||
ArrayNode arrayNode = (ArrayNode) mapper.readTree("[[3], [4, 5], []]");
|
||||
|
||||
List<Integer> list = evaluate(arrayNode, "['0']", List.class);
|
||||
assertThat(list).isInstanceOf(ArrayNodeAsList.class).containsExactly(3);
|
||||
list = evaluate(arrayNode, "['2']", List.class);
|
||||
assertThat(list).isInstanceOf(ArrayNodeAsList.class).isEmpty();
|
||||
|
||||
Integer number = evaluate(arrayNode, "['0'][0]", Integer.class);
|
||||
assertThat(number).isEqualTo(3);
|
||||
number = evaluate(arrayNode, "['1'][1]", Integer.class);
|
||||
assertThat(number).isEqualTo(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
void arrayProjection() throws Exception {
|
||||
JsonNode json = mapper.readTree(FOO_BAR_ARRAY_FIZZ_JSON);
|
||||
|
||||
// Filter the bar array to return only the fizz value of each element (to prove that SpEL considers bar
|
||||
// an array/list)
|
||||
List<?> actualArray = evaluate(json, "foo.bar.![fizz]", List.class);
|
||||
assertThat(actualArray).hasSize(3);
|
||||
assertThat(evaluate(actualArray, "[0]", Object.class)).isEqualTo(5);
|
||||
assertThat(evaluate(actualArray, "[1]", Object.class)).isEqualTo(7);
|
||||
assertThat(evaluate(actualArray, "[2]", Object.class)).isEqualTo(8);
|
||||
}
|
||||
|
||||
@Test
|
||||
void arraySelection() throws Exception {
|
||||
JsonNode json = mapper.readTree(FOO_BAR_ARRAY_FIZZ_JSON);
|
||||
|
||||
// Filter bar objects so that none match
|
||||
List<?> actualArray = evaluate(json, "foo.bar.?[fizz == 0]", List.class);
|
||||
assertThat(actualArray).isEmpty();
|
||||
|
||||
// Filter bar objects so that one match
|
||||
actualArray = evaluate(json, "foo.bar.?[fizz == 8]", List.class);
|
||||
assertThat(actualArray).hasSize(1);
|
||||
assertThat(((ComparableJsonNode) actualArray.get(0)).getRealNode()).isEqualTo(mapper.readTree("{\"fizz\": 8}"));
|
||||
|
||||
// Filter bar objects so several match
|
||||
actualArray = evaluate(json, "foo.bar.?[fizz > 6]", List.class);
|
||||
assertThat(actualArray).hasSize(2);
|
||||
assertThat(((ComparableJsonNode) actualArray.get(0)).getRealNode()).isEqualTo(mapper.readTree("{\"fizz\": 7}"));
|
||||
assertThat(((ComparableJsonNode) actualArray.get(1)).getRealNode()).isEqualTo(mapper.readTree("{\"fizz\": 8}"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedPropertyAccessViaJsonNode() throws Exception {
|
||||
JsonNode json = mapper.readTree(FOO_BAR_FIZZ_JSON);
|
||||
|
||||
assertThat(evaluate(json, "foo.bar", Integer.class)).isEqualTo(4);
|
||||
assertThat(evaluate(json, "foo.fizz", Integer.class)).isEqualTo(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
void noNullPointerExceptionWithCachedReadAccessor() throws Exception {
|
||||
Expression expression = parser.parseExpression("foo");
|
||||
JsonNode json1 = mapper.readTree(FOO_BAR_JSON);
|
||||
String value1 = expression.getValue(context, json1, String.class);
|
||||
assertThat(value1).isEqualTo("bar");
|
||||
JsonNode json2 = mapper.readTree("{}");
|
||||
Object value2 = expression.getValue(context, json2);
|
||||
assertThat(value2).isNull();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for JSON accessors that use a String-representation of a JSON document
|
||||
* as the root context object.
|
||||
*/
|
||||
@Nested
|
||||
class JsonAsStringTests {
|
||||
|
||||
@Test
|
||||
void selectorAccess() {
|
||||
String actual = evaluate(PROPERTY_NAMES_JSON, "property.^[name == 'value1'].name", String.class);
|
||||
assertThat(actual).isEqualTo("value1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedPropertyAccessViaJsonAsString() throws Exception {
|
||||
String json = FOO_BAR_FIZZ_JSON;
|
||||
|
||||
assertThat(evaluate(json, "foo.bar", Integer.class)).isEqualTo(4);
|
||||
assertThat(evaluate(json, "foo.fizz", Integer.class)).isEqualTo(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
void jsonGetValueConversionAsJsonNode() throws Exception {
|
||||
// use JsonNode conversion
|
||||
JsonNode node = evaluate(PROPERTY_NAMES_JSON, "property.^[name == 'value1']", JsonNode.class);
|
||||
assertThat(node).isEqualTo(mapper.readTree("{\"name\":\"value1\"}"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void jsonGetValueConversionAsObjectNode() throws Exception {
|
||||
// use ObjectNode conversion
|
||||
ObjectNode node = evaluate(PROPERTY_NAMES_JSON, "property.^[name == 'value1']", ObjectNode.class);
|
||||
assertThat(node).isEqualTo(mapper.readTree("{\"name\":\"value1\"}"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void jsonGetValueConversionAsArrayNode() throws Exception {
|
||||
// use ArrayNode conversion
|
||||
ArrayNode node = evaluate(PROPERTY_NAMES_JSON, "property", ArrayNode.class);
|
||||
assertThat(node).isEqualTo(mapper.readTree("[{\"name\":\"value1\"},{\"name\":\"value2\"}]"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void comparingArrayNode() throws Exception {
|
||||
Boolean actual = evaluate(PROPERTIES_WITH_NAMES_JSON, "property1 eq property2", Boolean.class);
|
||||
assertThat(actual).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void comparingJsonNode() throws Exception {
|
||||
Boolean actual = evaluate(PROPERTIES_WITH_NAMES_JSON, "property1[0] eq property2[0]", Boolean.class);
|
||||
assertThat(actual).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void unsupportedString() {
|
||||
String xml = "<what>?</what>";
|
||||
assertThatExceptionOfType(SpelEvaluationException.class)
|
||||
.isThrownBy(() -> evaluate(xml, "what", Object.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void unsupportedJson() {
|
||||
String json = "\"literal\"";
|
||||
assertThat(evaluate(json, "foo", Object.class)).isNull();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected <T> T evaluate(Object rootObject, String expression, Class<T> expectedType) {
|
||||
return parser.parseExpression(expression).getValue(context, rootObject, expectedType);
|
||||
}
|
||||
|
||||
private static final String FOO_BAR_JSON = """
|
||||
{
|
||||
"foo": "bar"
|
||||
}
|
||||
""";
|
||||
|
||||
private static final String FOO_BAR_FIZZ_JSON = """
|
||||
{
|
||||
"foo": {
|
||||
"bar": 4,
|
||||
"fizz": 5
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
private static final String FOO_BAR_ARRAY_FIZZ_JSON = """
|
||||
{
|
||||
"foo": {
|
||||
"bar": [
|
||||
{"fizz": 5, "buzz": 6},
|
||||
{"fizz": 7},
|
||||
{"fizz": 8}
|
||||
]
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
private static final String PROPERTY_NAMES_JSON = """
|
||||
{
|
||||
"property" : [
|
||||
{"name": "value1"},
|
||||
{"name": "value2"}
|
||||
]
|
||||
}
|
||||
""";
|
||||
|
||||
private static final String PROPERTIES_WITH_NAMES_JSON = """
|
||||
{
|
||||
"property1": [
|
||||
{"name": "value1"},
|
||||
{"name": "value2"}
|
||||
],
|
||||
"property2": [
|
||||
{"name": "value1"},
|
||||
{"name": "value2"}
|
||||
]
|
||||
}
|
||||
""";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright 2013-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.json;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.integration.json.JsonPropertyAccessor.ArrayNodeAsList;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link JsonIndexAccessor} combined with {@link JsonPropertyAccessor}.
|
||||
*
|
||||
* @author Sam Brannen
|
||||
* @since 6.4
|
||||
* @see JsonPropertyAccessorTests
|
||||
*/
|
||||
class JsonIndexAccessorTests extends AbstractJsonAccessorTests {
|
||||
|
||||
@BeforeEach
|
||||
void registerJsonAccessors() {
|
||||
context.addIndexAccessor(new JsonIndexAccessor());
|
||||
// We also register a JsonPropertyAccessor to ensure that the JsonIndexAccessor
|
||||
// does not interfere with the feature set of the JsonPropertyAccessor.
|
||||
context.addPropertyAccessor(new JsonPropertyAccessor());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests which index directly into a Jackson {@link ArrayNode}, which is only supported
|
||||
* by {@link JsonIndexAccessor}.
|
||||
*/
|
||||
@Nested
|
||||
class ArrayNodeTests {
|
||||
|
||||
@Test
|
||||
void indexDirectlyIntoArrayNodeWithIntegerIndex() throws Exception {
|
||||
ArrayNode arrayNode = (ArrayNode) mapper.readTree("[3, 4, 5]");
|
||||
Integer actual = evaluate(arrayNode, "[1]", Integer.class);
|
||||
assertThat(actual).isEqualTo(4);
|
||||
}
|
||||
|
||||
@Test
|
||||
void indexDirectlyIntoArrayNodeWithIntegerIndexForNullValue() throws Exception {
|
||||
ArrayNode arrayNode = (ArrayNode) mapper.readTree("[3, null, 5]");
|
||||
Integer actual = evaluate(arrayNode, "[1]", Integer.class);
|
||||
assertThat(actual).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void indexDirectlyIntoArrayNodeWithNegativeIntegerIndex() throws Exception {
|
||||
ArrayNode arrayNode = (ArrayNode) mapper.readTree("[3, 4, 5]");
|
||||
Integer actual = evaluate(arrayNode, "[-1]", Integer.class);
|
||||
// JsonIndexAccessor allows one to index into a JSON array via a negative index.
|
||||
assertThat(actual).isEqualTo(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
void indexDirectlyIntoArrayNodeWithNegativeIntegerIndexGreaterThanArrayLength() throws Exception {
|
||||
ArrayNode arrayNode = (ArrayNode) mapper.readTree("[3, 4, 5]");
|
||||
Integer actual = evaluate(arrayNode, "[-99]", Integer.class);
|
||||
// Although JsonIndexAccessor allows one to index into a JSON array via a negative
|
||||
// index, if the result of (array.length - index) is still negative, Jackson's
|
||||
// ArrayNode.get() method returns null instead of throwing an IndexOutOfBoundsException.
|
||||
assertThat(actual).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void indexDirectlyIntoArrayNodeWithIntegerIndexOutOfBounds() throws Exception {
|
||||
ArrayNode arrayNode = (ArrayNode) mapper.readTree("[3, 4, 5]");
|
||||
Integer actual = evaluate(arrayNode, "[9999]", Integer.class);
|
||||
// Jackson's ArrayNode.get() method always returns null instead of throwing an IndexOutOfBoundsException.
|
||||
assertThat(actual).isNull();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see AbstractJsonAccessorTests.JsonNodeTests#nestedArrayLookupWithStringIndexAndThenIntegerIndex()
|
||||
*/
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void nestedArrayLookupsWithIntegerIndexes() throws Exception {
|
||||
ArrayNode arrayNode = (ArrayNode) mapper.readTree("[[3], [4, 5], []]");
|
||||
|
||||
List<Integer> list = evaluate(arrayNode, "[0]", List.class);
|
||||
assertThat(list).isInstanceOf(ArrayNodeAsList.class).containsExactly(3);
|
||||
list = evaluate(arrayNode, "[2]", List.class);
|
||||
assertThat(list).isInstanceOf(ArrayNodeAsList.class).isEmpty();
|
||||
|
||||
Integer number = evaluate(arrayNode, "[0][0]", Integer.class);
|
||||
assertThat(number).isEqualTo(3);
|
||||
number = evaluate(arrayNode, "[1][1]", Integer.class);
|
||||
assertThat(number).isEqualTo(5);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2022 the original author or authors.
|
||||
* Copyright 2013-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,22 +16,13 @@
|
||||
|
||||
package org.springframework.integration.json;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.convert.converter.ConverterRegistry;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.spel.SpelEvaluationException;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.integration.json.JsonPropertyAccessor.ComparableJsonNode;
|
||||
import org.springframework.expression.spel.SpelMessage;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
@@ -43,232 +34,64 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
* @author Artem Bilan
|
||||
* @author Paul Martin
|
||||
* @author Pierre Lakreb
|
||||
* @author Sam Brannen
|
||||
*
|
||||
* @since 3.0
|
||||
* @see JsonIndexAccessorTests
|
||||
*/
|
||||
public class JsonPropertyAccessorTests {
|
||||
|
||||
private final SpelExpressionParser parser = new SpelExpressionParser();
|
||||
|
||||
private final StandardEvaluationContext context = new StandardEvaluationContext();
|
||||
|
||||
private final ObjectMapper mapper = new ObjectMapper();
|
||||
class JsonPropertyAccessorTests extends AbstractJsonAccessorTests {
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
void registerJsonPropertyAccessor() {
|
||||
context.addPropertyAccessor(new JsonPropertyAccessor());
|
||||
ConverterRegistry converterRegistry = (ConverterRegistry) DefaultConversionService.getSharedInstance();
|
||||
converterRegistry.addConverter(new JsonNodeWrapperToJsonNodeConverter());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleLookup() throws Exception {
|
||||
JsonNode json = mapper.readTree("{\"foo\": \"bar\"}");
|
||||
String value = evaluate(json, "foo", String.class);
|
||||
assertThat(value).isInstanceOf(String.class);
|
||||
assertThat(value).isEqualTo("bar");
|
||||
JsonNode json2 = mapper.readTree("{\"foo\": \"bar\"}");
|
||||
String value2 = evaluate(json2, "foo", String.class);
|
||||
assertThat(value2).isInstanceOf(String.class);
|
||||
assertThat(value.equals(value2)).isTrue();
|
||||
assertThat(value2.hashCode()).isEqualTo(value.hashCode());
|
||||
}
|
||||
/**
|
||||
* Tests which index directly into a Jackson {@link ArrayNode}, which is not supported
|
||||
* by {@link JsonPropertyAccessor}.
|
||||
*/
|
||||
@Nested
|
||||
class ArrayNodeTests {
|
||||
|
||||
@Test
|
||||
public void testTextNode() throws Exception {
|
||||
JsonNode json = mapper.readTree("\"foo\"");
|
||||
String result = evaluate(json, "#root", String.class);
|
||||
assertThat(result).isEqualTo("\"foo\"");
|
||||
}
|
||||
@Test
|
||||
void indexDirectlyIntoArrayNodeWithIntegerIndex() throws Exception {
|
||||
ArrayNode arrayNode = (ArrayNode) mapper.readTree("[3, 4, 5]");
|
||||
assertIndexingNotSupported(arrayNode, "[1]");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMissingProperty() throws Exception {
|
||||
JsonNode json = mapper.readTree("{\"foo\": \"bar\"}");
|
||||
assertThat(evaluate(json, "fizz", String.class)).isNull();
|
||||
}
|
||||
@Test
|
||||
void indexDirectlyIntoArrayNodeWithIntegerIndexForNullValue() throws Exception {
|
||||
ArrayNode arrayNode = (ArrayNode) mapper.readTree("[3, null, 5]");
|
||||
assertIndexingNotSupported(arrayNode, "[1]");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testArrayLookup() throws Exception {
|
||||
ArrayNode json = (ArrayNode) mapper.readTree("[3, 4, 5]");
|
||||
// Have to wrap the root array because ArrayNode itself is not a List
|
||||
Integer actual = evaluate(JsonPropertyAccessor.wrap(json), "[1]", Integer.class);
|
||||
assertThat(actual).isEqualTo(4);
|
||||
}
|
||||
@Test
|
||||
void indexDirectlyIntoArrayNodeWithNegativeIntegerIndex() throws Exception {
|
||||
ArrayNode arrayNode = (ArrayNode) mapper.readTree("[3, 4, 5]");
|
||||
assertIndexingNotSupported(arrayNode, "[-1]");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testArrayNegativeIndex() throws Exception {
|
||||
JsonNode json = mapper.readTree("{\"foo\":[3, 4, 5]}");
|
||||
// help access json list items with json-path negative index
|
||||
assertThat(evaluate(json, "foo[-1]", Integer.class)).isEqualTo(5);
|
||||
}
|
||||
@Test
|
||||
void indexDirectlyIntoArrayNodeWithIntegerIndexOutOfBounds() throws Exception {
|
||||
ArrayNode arrayNode = (ArrayNode) mapper.readTree("[3, 4, 5]");
|
||||
assertIndexingNotSupported(arrayNode, "[9999]");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testArrayIndexOutOfBounds() throws Exception {
|
||||
JsonNode json = mapper.readTree("{\"foo\":[3, 4, 5]}");
|
||||
assertThatExceptionOfType(SpelEvaluationException.class)
|
||||
.isThrownBy(() -> evaluate(json, "foo[3]", Object.class));
|
||||
}
|
||||
/**
|
||||
* @see AbstractJsonAccessorTests.JsonNodeTests#nestedArrayLookupWithStringIndexAndThenIntegerIndex()
|
||||
*/
|
||||
@Test
|
||||
void nestedArrayLookupWithIntegerIndexAndThenIntegerIndex() throws Exception {
|
||||
ArrayNode arrayNode = (ArrayNode) mapper.readTree("[[3], [4, 5], []]");
|
||||
assertIndexingNotSupported(arrayNode, "[1][1]");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testArrayLookupWithStringIndex() throws Exception {
|
||||
JsonNode json = mapper.readTree("[3, 4, 5]");
|
||||
Integer actual = evaluate(json, "['1']", Integer.class);
|
||||
assertThat(actual).isEqualTo(4);
|
||||
}
|
||||
private void assertIndexingNotSupported(ArrayNode arrayNode, String expression) {
|
||||
assertThatExceptionOfType(SpelEvaluationException.class)
|
||||
.isThrownBy(() -> parser.parseExpression(expression).getValue(context, arrayNode))
|
||||
.satisfies(ex -> assertThat(ex.getMessageCode()).isEqualTo(SpelMessage.INDEXING_NOT_SUPPORTED_FOR_TYPE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNestedArrayConstruct() throws Exception {
|
||||
ArrayNode json = (ArrayNode) mapper.readTree("[[3], [4, 5], []]");
|
||||
// JsonNode actual = evaluate("1.1", json, JsonNode.class); // Does not work
|
||||
Object actual = evaluate(JsonPropertyAccessor.wrap(json), "[1][1]", Object.class);
|
||||
assertThat(actual).isEqualTo(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNestedArrayConstructWithStringIndex() throws Exception {
|
||||
Object json = mapper.readTree("[[3], [4, 5], []]");
|
||||
Object actual = evaluate(json, "['1']['1']", Object.class);
|
||||
assertThat(actual).isEqualTo(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testArrayProjectionResult() throws Exception {
|
||||
Object json = mapper.readTree(
|
||||
"{\"foo\": {\"bar\": [ { \"fizz\": 5, \"buzz\": 6 }, {\"fizz\": 7}, {\"fizz\": 8} ] } }");
|
||||
// Filter the bar array to return only the fizz value of each element (to prove that SPeL considers bar
|
||||
// an array/list)
|
||||
List<?> actualArray = evaluate(json, "foo.bar.![fizz]", List.class);
|
||||
assertThat(actualArray).hasSize(3);
|
||||
assertThat(evaluate(actualArray, "[0]", Object.class)).isEqualTo(5);
|
||||
assertThat(evaluate(actualArray, "[1]", Object.class)).isEqualTo(7);
|
||||
assertThat(evaluate(actualArray, "[2]", Object.class)).isEqualTo(8);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFilterOnArraySelection() throws Exception {
|
||||
Object json = mapper.readTree(
|
||||
"{\"foo\": {\"bar\": [ { \"fizz\": 5, \"buzz\": 6 }, {\"fizz\": 7}, {\"fizz\": 8} ] } }");
|
||||
|
||||
// Filter bar objects so that none match
|
||||
List<?> actualArray = evaluate(json, "foo.bar.?[fizz == 0]", List.class);
|
||||
assertThat(actualArray).hasSize(0);
|
||||
|
||||
// Filter bar objects so that one match
|
||||
actualArray = evaluate(json, "foo.bar.?[fizz == 8]", List.class);
|
||||
assertThat(actualArray).hasSize(1);
|
||||
assertThat(((ComparableJsonNode) actualArray.get(0)).getRealNode()).isEqualTo(mapper.readTree("{\"fizz\": 8}"));
|
||||
|
||||
// Filter bar objects so several match
|
||||
actualArray = evaluate(json, "foo.bar.?[fizz > 6]", List.class);
|
||||
assertThat(actualArray).hasSize(2);
|
||||
assertThat(((ComparableJsonNode) actualArray.get(0)).getRealNode()).isEqualTo(mapper.readTree("{\"fizz\": 7}"));
|
||||
assertThat(((ComparableJsonNode) actualArray.get(1)).getRealNode()).isEqualTo(mapper.readTree("{\"fizz\": 8}"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNestedHashConstruct() throws Exception {
|
||||
Object json = mapper.readTree("{\"foo\": {\"bar\": 4, \"fizz\": 5} }");
|
||||
Object actual = evaluate(json, "foo.fizz", Object.class);
|
||||
assertThat(actual).isEqualTo(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImplicitStringConversion() {
|
||||
String json = "{\"foo\": {\"bar\": 4, \"fizz\": 5} }";
|
||||
Object actual = evaluate(json, "foo.fizz", Object.class);
|
||||
assertThat(actual).isEqualTo(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelectorAccess() {
|
||||
String json = "{\"property\":[{\"name\":\"value1\"},{\"name\":\"value2\"}]}";
|
||||
Object actual = evaluate(json, "property.^[name == 'value1'].name", Object.class);
|
||||
assertThat(actual).isEqualTo("value1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJsonGetValueConversionAsJsonNode() throws Exception {
|
||||
String json = "{\"property\":[{\"name\":\"value1\"},{\"name\":\"value2\"}]}";
|
||||
|
||||
// use JsonNode conversion
|
||||
Object node = evaluate(json, "property.^[name == 'value1']", JsonNode.class);
|
||||
assertThat(node).isInstanceOf(JsonNode.class);
|
||||
assertThat(((JsonNode) node)).isEqualTo(mapper.readTree("{\"name\":\"value1\"}"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJsonGetValueConversionAsObjectNode() throws Exception {
|
||||
String json = "{\"property\":[{\"name\":\"value1\"},{\"name\":\"value2\"}]}";
|
||||
|
||||
// use ObjectNode conversion
|
||||
Object node = evaluate(json, "property.^[name == 'value1']", JsonNode.class);
|
||||
assertThat(node).isInstanceOf(ObjectNode.class);
|
||||
assertThat(((ObjectNode) node)).isEqualTo(mapper.readTree("{\"name\":\"value1\"}"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJsonGetValueConversionAsArrayNode() throws Exception {
|
||||
String json = "{\"property\":[{\"name\":\"value1\"},{\"name\":\"value2\"}]}";
|
||||
|
||||
// use ArrayNode conversion
|
||||
Object node = evaluate(json, "property", ArrayNode.class);
|
||||
assertThat(node).isInstanceOf(ArrayNode.class);
|
||||
assertThat(((ArrayNode) node)).isEqualTo(mapper.readTree("[{\"name\":\"value1\"},{\"name\":\"value2\"}]"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJsonGetValueConversionAsString() {
|
||||
String json = "{\"property\":[{\"name\":\"value1\"},{\"name\":\"value2\"}]}";
|
||||
|
||||
// use ArrayNode conversion
|
||||
Object node = evaluate(json, "#root", String.class);
|
||||
assertThat(node).isInstanceOf(String.class);
|
||||
assertThat(((String) node)).isEqualTo("{\"property\":[{\"name\":\"value1\"},{\"name\":\"value2\"}]}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelectorComparingJsonNode() throws Exception {
|
||||
String json = "{\"property\":[{\"name\":\"value1\"},{\"name\":\"value2\"}], " +
|
||||
"\"property2\":[{\"name\":\"value1\"},{\"name\":\"value2\"}]}";
|
||||
Object actual = evaluate(json, "property[0] eq property2[0]", Object.class);
|
||||
assertThat(actual).isEqualTo(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSelectorComparingArrayNode() throws Exception {
|
||||
String json = "{\"property\":[{\"name\":\"value1\"},{\"name\":\"value2\"}], " +
|
||||
"\"property2\":[{\"name\":\"value1\"},{\"name\":\"value2\"}]}";
|
||||
Object actual = evaluate(json, "property eq property2", Object.class);
|
||||
assertThat(actual).isEqualTo(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnsupportedString() {
|
||||
String xml = "<what>?</what>";
|
||||
assertThatExceptionOfType(SpelEvaluationException.class)
|
||||
.isThrownBy(() -> evaluate(xml, "what", Object.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnsupportedJson() {
|
||||
String json = "\"literal\"";
|
||||
assertThat(evaluate(json, "foo", Object.class)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoNullPointerWithCachedReadAccessor() throws Exception {
|
||||
Expression expression = parser.parseExpression("foo");
|
||||
Object json = mapper.readTree("{\"foo\": \"bar\"}");
|
||||
Object value = expression.getValue(this.context, json);
|
||||
assertThat(value).isInstanceOf(String.class);
|
||||
assertThat(value).isEqualTo("bar");
|
||||
Object json2 = mapper.readTree("{}");
|
||||
Object value2 = expression.getValue(this.context, json2);
|
||||
assertThat(value2).isNull();
|
||||
}
|
||||
|
||||
private <T> T evaluate(Object target, String expression, Class<T> expectedType) {
|
||||
return parser.parseExpression(expression).getValue(context, target, expectedType);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user