Make JsonPropertyAccessor returned type directly
* add `Comparable` contract to the initial wrapper so that expressions containing selection/projection with JsonNode filtering are now possible * render directly the value of a ValueNode. SpEL parser can now evaluate correctly the object value when the expression deals with filtering on `JsonNode` values (<, >, ==...) * the 2 above changes make this possible: `property.^[name.getTarget().asText() == 'value1'].name` can now be written as simply as `property.^[name == 'value1'].name`. The expression is now fully compatible with other PropertyAccessor types * rename `ToStringFriendlyJsonNode` to `JsonNodeWrapper` as it reflects more the capability of the new wrapper * add a GenericConverter to be able to convert the `JsonNodeWrapper` class into a `JsonNode` (or its derivative classes) directly while calling `expression.getValue(..., JsonNode.class)` * add the possibility to access list items in a json-path like index => a negative number will begin from the end of the list <= * return null value when `JsonNode` cannot handle the property name (or index) * Change Json Converter * Clean up the code style * Convert JsonPropertyAccessor tests to JUnit 5
This commit is contained in:
committed by
Artem Bilan
parent
7a84d6797e
commit
b58b0e5ffa
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2019 the original author or authors.
|
||||
* Copyright 2013-2021 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.
|
||||
@@ -26,7 +26,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
@@ -51,6 +51,7 @@ import org.springframework.messaging.support.GenericMessage;
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
* @author Pierre Lakreb
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
@@ -167,25 +168,25 @@ public class ParentContextTests {
|
||||
assertThat(out.getPayload()).isEqualTo("FOO");
|
||||
|
||||
assertThat(parent
|
||||
.containsBean(IntegrationContextUtils.TO_STRING_FRIENDLY_JSON_NODE_TO_STRING_CONVERTER_BEAN_NAME))
|
||||
.containsBean(IntegrationContextUtils.JSON_NODE_WRAPPER_TO_JSON_NODE_CONVERTER))
|
||||
.isTrue();
|
||||
|
||||
assertThat(child
|
||||
.containsBean(IntegrationContextUtils.TO_STRING_FRIENDLY_JSON_NODE_TO_STRING_CONVERTER_BEAN_NAME))
|
||||
.containsBean(IntegrationContextUtils.JSON_NODE_WRAPPER_TO_JSON_NODE_CONVERTER))
|
||||
.isTrue();
|
||||
|
||||
Object converterRegistrar = parent.getBean(IntegrationContextUtils.CONVERTER_REGISTRAR_BEAN_NAME);
|
||||
assertThat(converterRegistrar).isNotNull();
|
||||
Set<?> converters = TestUtils.getPropertyValue(converterRegistrar, "converters", Set.class);
|
||||
boolean toStringFriendlyJsonNodeToStringConverterPresent = false;
|
||||
boolean jsonNodeWrapperToJsonNodeConverterPresent = false;
|
||||
for (Object converter : converters) {
|
||||
if ("ToStringFriendlyJsonNodeToStringConverter".equals(converter.getClass().getSimpleName())) {
|
||||
toStringFriendlyJsonNodeToStringConverterPresent = true;
|
||||
if ("JsonNodeWrapperToJsonNodeConverter".equals(converter.getClass().getSimpleName())) {
|
||||
jsonNodeWrapperToJsonNodeConverterPresent = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assertThat(toStringFriendlyJsonNodeToStringConverterPresent).isTrue();
|
||||
assertThat(jsonNodeWrapperToJsonNodeConverterPresent).isTrue();
|
||||
|
||||
MessageChannel input = parent.getBean("testJsonNodeToStringConverterInputChannel", MessageChannel.class);
|
||||
PollableChannel output = parent.getBean("testJsonNodeToStringConverterOutputChannel", PollableChannel.class);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2019 the original author or authors.
|
||||
* Copyright 2013-2021 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.
|
||||
@@ -17,20 +17,25 @@
|
||||
package org.springframework.integration.json;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
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 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;
|
||||
|
||||
/**
|
||||
* Tests for {@link JsonPropertyAccessor}.
|
||||
@@ -38,6 +43,8 @@ import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
* @author Eric Bottard
|
||||
* @author Artem Bilan
|
||||
* @author Paul Martin
|
||||
* @author Pierre Lakreb
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
public class JsonPropertyAccessorTests {
|
||||
@@ -48,63 +55,66 @@ public class JsonPropertyAccessorTests {
|
||||
|
||||
private final ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
@Before
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
context.addPropertyAccessor(new JsonPropertyAccessor());
|
||||
ConverterRegistry converterRegistry = (ConverterRegistry) DefaultConversionService.getSharedInstance();
|
||||
converterRegistry.addConverter(new JsonNodeWrapperToJsonNodeConverter());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimpleLookup() throws Exception {
|
||||
Object json = mapper.readTree("{\"foo\": \"bar\"}");
|
||||
Object value = evaluate(json, "foo", Object.class);
|
||||
assertThat(value).isInstanceOf(JsonPropertyAccessor.ToStringFriendlyJsonNode.class);
|
||||
assertThat(value.toString()).isEqualTo("bar");
|
||||
Object json2 = mapper.readTree("{\"foo\": \"bar\"}");
|
||||
Object value2 = evaluate(json2, "foo", Object.class);
|
||||
assertThat(value2).isInstanceOf(JsonPropertyAccessor.ToStringFriendlyJsonNode.class);
|
||||
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());
|
||||
}
|
||||
|
||||
@Test(expected = SpelEvaluationException.class)
|
||||
public void testUnsupportedJsonConstruct() throws Exception {
|
||||
Object json = mapper.readTree("\"foo\"");
|
||||
evaluate(json, "fizz", Object.class);
|
||||
@Test
|
||||
public void testTextNode() throws Exception {
|
||||
JsonNode json = mapper.readTree("\"foo\"");
|
||||
String result = evaluate(json, "#root", String.class);
|
||||
assertThat(result).isEqualTo("\"foo\"");
|
||||
}
|
||||
|
||||
@Test(expected = SpelEvaluationException.class)
|
||||
@Test
|
||||
public void testMissingProperty() throws Exception {
|
||||
Object json = mapper.readTree("{\"foo\": \"bar\"}");
|
||||
evaluate(json, "fizz", Object.class);
|
||||
JsonNode json = mapper.readTree("{\"foo\": \"bar\"}");
|
||||
assertThat(evaluate(json, "fizz", String.class)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testArrayLookup() throws Exception {
|
||||
ArrayNode json = (ArrayNode) mapper.readTree("[3, 4, 5]");
|
||||
// JsonNode actual = evaluate("1", json, JsonNode.class); // Does not work
|
||||
// Have to wrap the root array because ArrayNode itself is not a container
|
||||
Object actual = evaluate(JsonPropertyAccessor.wrap(json), "[1]", Object.class);
|
||||
assertThat(actual.toString()).isEqualTo("4");
|
||||
// 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
|
||||
public void testArrayNegativeIndex() throws Exception {
|
||||
JsonNode json = mapper.readTree("{\"foo\":[3, 4, 5]}");
|
||||
assertThat(evaluate(json, "foo[-1]", Object.class)).isNull();
|
||||
// help access json list items with json-path negative index
|
||||
assertThat(evaluate(json, "foo[-1]", Integer.class)).isEqualTo(5);
|
||||
}
|
||||
|
||||
@Test(expected = SpelEvaluationException.class)
|
||||
@Test
|
||||
public void testArrayIndexOutOfBounds() throws Exception {
|
||||
JsonNode json = mapper.readTree("{\"foo\":[3, 4, 5]}");
|
||||
evaluate(json, "foo[3]", Object.class);
|
||||
assertThatExceptionOfType(SpelEvaluationException.class)
|
||||
.isThrownBy(() -> evaluate(json, "foo[3]", Object.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testArrayLookupWithStringIndex() throws Exception {
|
||||
Object json = mapper.readTree("[3, 4, 5]");
|
||||
// JsonNode actual = evaluate("'1'", json, JsonNode.class); // Does not work
|
||||
Object actual = evaluate(json, "['1']", Object.class);
|
||||
assertThat(actual.toString()).isEqualTo("4");
|
||||
JsonNode json = mapper.readTree("[3, 4, 5]");
|
||||
Integer actual = evaluate(json, "['1']", Integer.class);
|
||||
assertThat(actual).isEqualTo(4);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -112,63 +122,138 @@ public class JsonPropertyAccessorTests {
|
||||
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.toString()).isEqualTo("5");
|
||||
assertThat(actual).isEqualTo(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNestedArrayConstructWithStringIndex() throws Exception {
|
||||
Object json = mapper.readTree("[[3], [4, 5], []]");
|
||||
// JsonNode actual = evaluate("1.1", json, JsonNode.class); // Does not work
|
||||
Object actual = evaluate(json, "['1']['1']", Object.class);
|
||||
assertThat(actual.toString()).isEqualTo("5");
|
||||
assertThat(actual).isEqualTo(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testArrayResult() throws Exception {
|
||||
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.size()).as("Array size").isEqualTo(3);
|
||||
assertThat(evaluate(actualArray, "[0]", Object.class).toString()).as("[0]").isEqualTo("5");
|
||||
assertThat(evaluate(actualArray, "[1]", Object.class).toString()).as("[1]").isEqualTo("7");
|
||||
assertThat(evaluate(actualArray, "[2]", Object.class).toString()).as("[2]").isEqualTo("8");
|
||||
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 testEmptyArrayResult() throws Exception {
|
||||
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.size()).isEqualTo(0);
|
||||
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.toString()).isEqualTo("5");
|
||||
assertThat(actual).isEqualTo(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testImplicitStringConversion() throws Exception {
|
||||
public void testImplicitStringConversion() {
|
||||
String json = "{\"foo\": {\"bar\": 4, \"fizz\": 5} }";
|
||||
Object actual = evaluate(json, "foo.fizz", Object.class);
|
||||
assertThat(actual.toString()).isEqualTo("5");
|
||||
assertThat(actual).isEqualTo(5);
|
||||
}
|
||||
|
||||
@Test(expected = SpelEvaluationException.class)
|
||||
public void testUnsupportedString() throws Exception {
|
||||
@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>";
|
||||
evaluate(xml, "what", Object.class);
|
||||
assertThatExceptionOfType(SpelEvaluationException.class)
|
||||
.isThrownBy(() -> evaluate(xml, "what", Object.class));
|
||||
}
|
||||
|
||||
@Test(expected = SpelEvaluationException.class)
|
||||
public void testUnsupportedJson() throws Exception {
|
||||
@Test
|
||||
public void testUnsupportedJson() {
|
||||
String json = "\"literal\"";
|
||||
evaluate(json, "foo", Object.class);
|
||||
assertThat(evaluate(json, "foo", Object.class)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -176,8 +261,8 @@ public class JsonPropertyAccessorTests {
|
||||
Expression expression = parser.parseExpression("foo");
|
||||
Object json = mapper.readTree("{\"foo\": \"bar\"}");
|
||||
Object value = expression.getValue(this.context, json);
|
||||
assertThat(value).isInstanceOf(JsonPropertyAccessor.ToStringFriendlyJsonNode.class);
|
||||
assertThat(value.toString()).isEqualTo("bar");
|
||||
assertThat(value).isInstanceOf(String.class);
|
||||
assertThat(value).isEqualTo("bar");
|
||||
Object json2 = mapper.readTree("{}");
|
||||
Object value2 = expression.getValue(this.context, json2);
|
||||
assertThat(value2).isNull();
|
||||
|
||||
Reference in New Issue
Block a user