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
@@ -86,6 +86,7 @@ import org.springframework.util.ClassUtils;
|
||||
* @author Artem Bilan
|
||||
* @author Gary Russell
|
||||
* @author Michael Wiles
|
||||
* @author Pierre Lakreb
|
||||
*
|
||||
* @see IntegrationContextUtils
|
||||
*/
|
||||
@@ -411,17 +412,17 @@ class DefaultConfiguringBeanFactoryPostProcessor
|
||||
|
||||
private void jsonNodeToString(int registryId) {
|
||||
if (!this.beanFactory.containsBean(
|
||||
IntegrationContextUtils.TO_STRING_FRIENDLY_JSON_NODE_TO_STRING_CONVERTER_BEAN_NAME) &&
|
||||
IntegrationContextUtils.JSON_NODE_WRAPPER_TO_JSON_NODE_CONVERTER) &&
|
||||
!REGISTRIES_PROCESSED.contains(registryId) && JacksonPresent.isJackson2Present()) {
|
||||
|
||||
this.registry.registerBeanDefinition(
|
||||
IntegrationContextUtils.TO_STRING_FRIENDLY_JSON_NODE_TO_STRING_CONVERTER_BEAN_NAME,
|
||||
IntegrationContextUtils.JSON_NODE_WRAPPER_TO_JSON_NODE_CONVERTER,
|
||||
BeanDefinitionBuilder.genericBeanDefinition(IntegrationConfigUtils.BASE_PACKAGE +
|
||||
".json.ToStringFriendlyJsonNodeToStringConverter")
|
||||
".json.JsonNodeWrapperToJsonNodeConverter")
|
||||
.getBeanDefinition());
|
||||
INTEGRATION_CONVERTER_INITIALIZER.registerConverter(this.registry,
|
||||
new RuntimeBeanReference(
|
||||
IntegrationContextUtils.TO_STRING_FRIENDLY_JSON_NODE_TO_STRING_CONVERTER_BEAN_NAME));
|
||||
IntegrationContextUtils.JSON_NODE_WRAPPER_TO_JSON_NODE_CONVERTER));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-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.
|
||||
@@ -40,6 +40,7 @@ import org.springframework.util.Assert;
|
||||
* @author Artem Bilan
|
||||
* @author Gary Russell
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Pierre Lakreb
|
||||
*/
|
||||
public abstract class IntegrationContextUtils {
|
||||
|
||||
@@ -91,8 +92,7 @@ public abstract class IntegrationContextUtils {
|
||||
|
||||
public static final String GLOBAL_CHANNEL_INTERCEPTOR_PROCESSOR_BEAN_NAME = "globalChannelInterceptorProcessor";
|
||||
|
||||
public static final String TO_STRING_FRIENDLY_JSON_NODE_TO_STRING_CONVERTER_BEAN_NAME =
|
||||
"toStringFriendlyJsonNodeToStringConverter";
|
||||
public static final String JSON_NODE_WRAPPER_TO_JSON_NODE_CONVERTER = "jsonNodeWrapperToJsonNodeConverter";
|
||||
|
||||
public static final String INTEGRATION_LIFECYCLE_ROLE_CONTROLLER = "integrationLifecycleRoleController";
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 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.
|
||||
* 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.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.convert.converter.GenericConverter;
|
||||
import org.springframework.integration.json.JsonPropertyAccessor.JsonNodeWrapper;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
/**
|
||||
* The {@link Converter} implementation for the conversion of {@link JsonPropertyAccessor.JsonNodeWrapper} to
|
||||
* {@link JsonNode}, when the {@link JsonPropertyAccessor.JsonNodeWrapper} can be a result of the expression
|
||||
* for JSON in case of the {@link JsonPropertyAccessor} usage.
|
||||
*
|
||||
* @author Pierre Lakreb
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 5.5
|
||||
*/
|
||||
class JsonNodeWrapperToJsonNodeConverter implements GenericConverter {
|
||||
|
||||
@Override
|
||||
public Set<ConvertiblePair> getConvertibleTypes() {
|
||||
return Collections.singleton(new ConvertiblePair(JsonNodeWrapper.class, JsonNode.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
|
||||
return targetType.getObjectType().cast(((JsonNodeWrapper<?>) source).getRealNode());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.integration.json;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.AbstractList;
|
||||
import java.util.Iterator;
|
||||
|
||||
@@ -23,14 +24,13 @@ import org.springframework.expression.AccessException;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.PropertyAccessor;
|
||||
import org.springframework.expression.TypedValue;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
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.ContainerNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
/**
|
||||
* A SpEL {@link PropertyAccessor} that knows how to read properties from JSON objects.
|
||||
@@ -40,6 +40,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
* @author Artem Bilan
|
||||
* @author Paul Martin
|
||||
* @author Gary Russell
|
||||
* @author Pierre Lakreb
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
@@ -49,15 +50,12 @@ public class JsonPropertyAccessor implements PropertyAccessor {
|
||||
* The kind of types this can work with.
|
||||
*/
|
||||
private static final Class<?>[] SUPPORTED_CLASSES =
|
||||
new Class<?>[] {
|
||||
{
|
||||
String.class,
|
||||
ToStringFriendlyJsonNode.class,
|
||||
ArrayNodeAsList.class,
|
||||
ObjectNode.class,
|
||||
ArrayNode.class
|
||||
JsonNodeWrapper.class,
|
||||
JsonNode.class
|
||||
};
|
||||
|
||||
// Note: ObjectMapper is thread-safe
|
||||
private ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
public void setObjectMapper(ObjectMapper objectMapper) {
|
||||
@@ -72,42 +70,24 @@ public class JsonPropertyAccessor implements PropertyAccessor {
|
||||
|
||||
@Override
|
||||
public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException {
|
||||
ContainerNode<?> container = asJson(target);
|
||||
JsonNode node = asJson(target);
|
||||
Integer index = maybeIndex(name);
|
||||
if (container instanceof ArrayNode) {
|
||||
if (node instanceof ArrayNode) {
|
||||
return index != null;
|
||||
}
|
||||
else {
|
||||
return ((index != null && container.has(index)) || container.has(name));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private ContainerNode<?> assertContainerNode(JsonNode json) throws AccessException {
|
||||
if (json instanceof ContainerNode) {
|
||||
return (ContainerNode<?>) json;
|
||||
private JsonNode asJson(Object target) throws AccessException {
|
||||
if (target instanceof JsonNode) {
|
||||
return (JsonNode) target;
|
||||
}
|
||||
else {
|
||||
throw new AccessException(
|
||||
"Can not act on json that is not a ContainerNode: " + json.getClass().getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
private ContainerNode<?> asJson(Object target) throws AccessException {
|
||||
if (target instanceof ContainerNode) {
|
||||
return (ContainerNode<?>) target;
|
||||
}
|
||||
else if (target instanceof ToStringFriendlyJsonNode) {
|
||||
ToStringFriendlyJsonNode wrapper = (ToStringFriendlyJsonNode) target;
|
||||
return assertContainerNode(wrapper.node);
|
||||
}
|
||||
else if (target instanceof ArrayNodeAsList) {
|
||||
ArrayNodeAsList wrapper = (ArrayNodeAsList) target;
|
||||
return assertContainerNode(wrapper.node);
|
||||
else if (target instanceof JsonNodeWrapper) {
|
||||
return ((JsonNodeWrapper<?>) target).getRealNode();
|
||||
}
|
||||
else if (target instanceof String) {
|
||||
try {
|
||||
JsonNode json = this.objectMapper.readTree((String) target);
|
||||
return assertContainerNode(json);
|
||||
return this.objectMapper.readTree((String) target);
|
||||
}
|
||||
catch (JsonProcessingException e) {
|
||||
throw new AccessException("Exception while trying to deserialize String", e);
|
||||
@@ -121,7 +101,7 @@ public class JsonPropertyAccessor implements PropertyAccessor {
|
||||
/**
|
||||
* Return an integer if the String property name can be parsed as an int, or null otherwise.
|
||||
*/
|
||||
private Integer maybeIndex(String name) {
|
||||
private static Integer maybeIndex(String name) {
|
||||
try {
|
||||
return Integer.valueOf(name);
|
||||
}
|
||||
@@ -131,14 +111,14 @@ public class JsonPropertyAccessor implements PropertyAccessor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException {
|
||||
ContainerNode<?> container = asJson(target);
|
||||
public TypedValue read(EvaluationContext context, @Nullable Object target, String name) throws AccessException {
|
||||
JsonNode node = asJson(target);
|
||||
Integer index = maybeIndex(name);
|
||||
if (index != null && container.has(index)) {
|
||||
return typedValue(container.get(index));
|
||||
if (index != null && node.has(index)) {
|
||||
return typedValue(node.get(index));
|
||||
}
|
||||
else {
|
||||
return typedValue(container.get(name));
|
||||
return typedValue(node.get(name));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,138 +132,163 @@ public class JsonPropertyAccessor implements PropertyAccessor {
|
||||
throw new UnsupportedOperationException("Write is not supported");
|
||||
}
|
||||
|
||||
private static TypedValue typedValue(JsonNode json) {
|
||||
private static TypedValue typedValue(JsonNode json) throws AccessException {
|
||||
if (json == null) {
|
||||
return TypedValue.NULL;
|
||||
}
|
||||
else {
|
||||
return new TypedValue(wrap(json));
|
||||
else if (json.isValueNode()) {
|
||||
return new TypedValue(getValue(json));
|
||||
}
|
||||
return new TypedValue(wrap(json));
|
||||
}
|
||||
|
||||
public static WrappedJsonNode<?> wrap(JsonNode json) {
|
||||
private static Object getValue(JsonNode json) throws AccessException {
|
||||
if (json.isTextual()) {
|
||||
return json.textValue();
|
||||
}
|
||||
else if (json.isNumber()) {
|
||||
return json.numberValue();
|
||||
}
|
||||
else if (json.isBoolean()) {
|
||||
return json.asBoolean();
|
||||
}
|
||||
else if (json.isNull()) {
|
||||
return null;
|
||||
}
|
||||
else if (json.isBinary()) {
|
||||
try {
|
||||
return json.binaryValue();
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new AccessException(
|
||||
"Can not get content of binary value : " + json);
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Json is not ValueNode.");
|
||||
}
|
||||
|
||||
public static Object wrap(JsonNode json) throws AccessException {
|
||||
if (json == null) {
|
||||
return null;
|
||||
}
|
||||
else if (json instanceof ArrayNode) {
|
||||
return new ArrayNodeAsList((ArrayNode) json);
|
||||
}
|
||||
else if (json.isValueNode()) {
|
||||
return getValue(json);
|
||||
}
|
||||
else {
|
||||
return new ToStringFriendlyJsonNode(json);
|
||||
return new ComparableJsonNode(json);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The base interface for wrapped {@link JsonNode}.
|
||||
* @since 5.0
|
||||
*/
|
||||
public interface WrappedJsonNode<T extends JsonNode> {
|
||||
interface JsonNodeWrapper<T> extends Comparable<T> {
|
||||
|
||||
/**
|
||||
* Return the wrapped {@link JsonNode}
|
||||
* @return the wrapped JsonNode
|
||||
*/
|
||||
T getTarget();
|
||||
String toString();
|
||||
|
||||
JsonNode getRealNode();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link WrappedJsonNode} implementation to represent {@link JsonNode} as string.
|
||||
*/
|
||||
public static class ToStringFriendlyJsonNode implements WrappedJsonNode<JsonNode> {
|
||||
static class ComparableJsonNode implements JsonNodeWrapper<ComparableJsonNode> {
|
||||
|
||||
private final JsonNode node;
|
||||
private final JsonNode delegate;
|
||||
|
||||
ToStringFriendlyJsonNode(JsonNode node) {
|
||||
this.node = node;
|
||||
ComparableJsonNode(JsonNode delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonNode getTarget() {
|
||||
return this.node;
|
||||
public JsonNode getRealNode() {
|
||||
return this.delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (this.node == null) {
|
||||
return "null";
|
||||
}
|
||||
if (this.node.isValueNode()) {
|
||||
// This is to avoid quotes around a TextNode for example
|
||||
return this.node.asText();
|
||||
}
|
||||
else {
|
||||
return this.node.toString();
|
||||
}
|
||||
return this.delegate.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
ToStringFriendlyJsonNode that = (ToStringFriendlyJsonNode) o;
|
||||
return (this.node == that.node) || (this.node != null && this.node.equals(that.node));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.node != null ? this.node.toString().hashCode() : 0;
|
||||
public int compareTo(ComparableJsonNode o) {
|
||||
return this.delegate.equals(o.delegate) ? 0 : 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* An {@link AbstractList} implementation around {@link ArrayNode} with {@link WrappedJsonNode} aspect.
|
||||
* An {@link AbstractList} implementation around {@link ArrayNode} with {@link JsonNodeWrapper} aspect.
|
||||
* @since 5.0
|
||||
*/
|
||||
public static class ArrayNodeAsList extends AbstractList<WrappedJsonNode<?>>
|
||||
implements WrappedJsonNode<ArrayNode> {
|
||||
static class ArrayNodeAsList extends AbstractList<Object> implements JsonNodeWrapper<Object> {
|
||||
|
||||
private final ArrayNode node;
|
||||
private final ArrayNode delegate;
|
||||
|
||||
ArrayNodeAsList(ArrayNode node) {
|
||||
this.node = node;
|
||||
this.delegate = node;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayNode getTarget() {
|
||||
return this.node;
|
||||
public JsonNode getRealNode() {
|
||||
return this.delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappedJsonNode<?> get(int index) {
|
||||
return wrap(this.node.get(index));
|
||||
public String toString() {
|
||||
return this.delegate.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object get(int index) {
|
||||
if (index < 0) {
|
||||
// negative index can be handled with that conversion
|
||||
index = this.delegate.size() + index;
|
||||
}
|
||||
try {
|
||||
return wrap(this.delegate.get(index));
|
||||
}
|
||||
catch (AccessException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return this.node.size();
|
||||
return this.delegate.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<WrappedJsonNode<?>> iterator() {
|
||||
public Iterator<Object> iterator() {
|
||||
|
||||
return new Iterator<WrappedJsonNode<?>>() {
|
||||
return new Iterator<Object>() {
|
||||
|
||||
private final Iterator<JsonNode> delegate = ArrayNodeAsList.this.node.iterator();
|
||||
private final Iterator<JsonNode> it = ArrayNodeAsList.this.delegate.iterator();
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return this.delegate.hasNext();
|
||||
return this.it.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappedJsonNode<?> next() {
|
||||
return wrap(this.delegate.next());
|
||||
public Object next() {
|
||||
try {
|
||||
return wrap(this.it.next());
|
||||
}
|
||||
catch (AccessException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2019 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 org.springframework.core.convert.converter.Converter;
|
||||
|
||||
/**
|
||||
* The {@link Converter} implementation for the conversion of
|
||||
* {@link JsonPropertyAccessor.ToStringFriendlyJsonNode} to {@link String},
|
||||
* when the {@link JsonPropertyAccessor.ToStringFriendlyJsonNode} can be a result of the
|
||||
* expression for JSON in case of the {@link JsonPropertyAccessor} usage.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
* @since 4.1
|
||||
*/
|
||||
class ToStringFriendlyJsonNodeToStringConverter
|
||||
implements Converter<JsonPropertyAccessor.ToStringFriendlyJsonNode, String> {
|
||||
|
||||
@Override
|
||||
public String convert(JsonPropertyAccessor.ToStringFriendlyJsonNode source) {
|
||||
return source.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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