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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user