diff --git a/build.gradle b/build.gradle
index 52d4d80150..e7a2bca870 100644
--- a/build.gradle
+++ b/build.gradle
@@ -51,7 +51,7 @@ subprojects { subproject ->
groovyVersion = '2.1.0'
hamcrestVersion = '1.3'
jacksonVersion = '1.9.2'
- jackson2Version = '2.1.2'
+ jackson2Version = '2.2.3'
javaxActivationVersion = '1.1.1'
junitVersion = '4.11'
log4jVersion = '1.2.12'
diff --git a/spring-integration-core/src/main/java/org/springframework/integration/json/JsonPropertyAccessor.java b/spring-integration-core/src/main/java/org/springframework/integration/json/JsonPropertyAccessor.java
new file mode 100644
index 0000000000..5c41cfb7a6
--- /dev/null
+++ b/spring-integration-core/src/main/java/org/springframework/integration/json/JsonPropertyAccessor.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2013 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
+ *
+ * http://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.io.IOException;
+
+import org.springframework.expression.AccessException;
+import org.springframework.expression.EvaluationContext;
+import org.springframework.expression.PropertyAccessor;
+import org.springframework.expression.TypedValue;
+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 on Jackson JSON objects.
+ *
+ * @author Eric Bottard
+ */
+public class JsonPropertyAccessor implements PropertyAccessor {
+
+ /**
+ * The kind of types this can work with.
+ */
+ private static final Class>[] SUPPORTED_CLASSES =
+ new Class[] { String.class, ObjectNode.class, ArrayNode.class };
+
+ // Note: ObjectMapper is thread-safe
+ private ObjectMapper objectMapper = new ObjectMapper();
+
+ @Override
+ public Class>[] getSpecificTargetClasses() {
+ return SUPPORTED_CLASSES;
+ }
+
+ @Override
+ public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException {
+ ContainerNode> container = asJson(target);
+ Integer index = maybeIndex(name);
+ return ((index != null && container.has(index)) || container.has(name));
+ }
+
+ private ContainerNode> asJson(Object target) throws AccessException {
+ if (target instanceof ContainerNode) {
+ return (ContainerNode>) target;
+ }
+ else if (target instanceof String) {
+ try {
+ JsonNode json = this.objectMapper.readTree((String) target);
+ if (json instanceof ContainerNode) {
+ return (ContainerNode>) json;
+ }
+ else {
+ throw new AccessException("Can not act on json that is not a ContainerNode: "
+ + json.getClass().getSimpleName());
+ }
+ }
+ catch (JsonProcessingException e) {
+ throw new AccessException("Exception while trying to deserialize String", e);
+ }
+ catch (IOException e) {
+ throw new AccessException("Exception while trying to deserialize String", e);
+ }
+ }
+ else {
+ throw new IllegalStateException("Can't happen. Check SUPPORTED_CLASSES");
+ }
+ }
+
+ /**
+ * Return an integer if the String property name can be parsed as an int, or null otherwise.
+ */
+ private Integer maybeIndex(String name) {
+ try {
+ return Integer.valueOf(name);
+ }
+ catch (NumberFormatException e) {
+ return null;
+ }
+ }
+
+ @Override
+ public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException {
+ ContainerNode> container = asJson(target);
+ Integer index = maybeIndex(name);
+ if (index != null && container.has(index)) {
+ return new TypedValue(container.get(index));
+ }
+ else {
+ return new TypedValue(container.get(name));
+ }
+ }
+
+ @Override
+ public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException {
+ return false;
+ }
+
+ @Override
+ public void write(EvaluationContext context, Object target, String name, Object newValue) throws AccessException {
+ throw new UnsupportedOperationException("Write is not supported");
+ }
+
+ public void setObjectMapper(ObjectMapper objectMapper) {
+ Assert.notNull(objectMapper, "'objectMapper' cannot be null");
+ this.objectMapper = objectMapper;
+ }
+
+}
diff --git a/spring-integration-core/src/test/java/org/springframework/integration/json/JsonPropertyAccessorTests.java b/spring-integration-core/src/test/java/org/springframework/integration/json/JsonPropertyAccessorTests.java
new file mode 100644
index 0000000000..3dfa07b214
--- /dev/null
+++ b/spring-integration-core/src/test/java/org/springframework/integration/json/JsonPropertyAccessorTests.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2013 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
+ *
+ * http://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 static org.junit.Assert.assertEquals;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.springframework.expression.spel.SpelEvaluationException;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.expression.spel.support.StandardEvaluationContext;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * Tests for {@link JsonPropertyAccessor}.
+ *
+ * @author Eric Bottard
+ */
+public class JsonPropertyAccessorTests {
+
+ private final SpelExpressionParser parser = new SpelExpressionParser();
+
+ private final StandardEvaluationContext context = new StandardEvaluationContext();
+
+ private final ObjectMapper mapper = new ObjectMapper();
+
+ @Before
+ public void setup() {
+ context.addPropertyAccessor(new JsonPropertyAccessor());
+ }
+
+ @Test
+ public void testSimpleLookup() throws Exception {
+ Object json = mapper.readTree("{\"foo\": \"bar\"}");
+ JsonNode actual = evaluate(json, "foo", JsonNode.class);
+ assertEquals("bar", actual.asText());
+ }
+
+ @Test(expected = SpelEvaluationException.class)
+ public void testUnsupportedJsonConstruct() throws Exception {
+ Object json = mapper.readTree("\"foo\"");
+ evaluate(json, "fizz", JsonNode.class);
+ }
+
+ @Test(expected = SpelEvaluationException.class)
+ public void testMissingProperty() throws Exception {
+ Object json = mapper.readTree("{\"foo\": \"bar\"}");
+ evaluate(json, "fizz", JsonNode.class);
+ }
+
+ @Test
+ public void testArrayLookup() throws Exception {
+ Object json = mapper.readTree("[3, 4, 5]");
+ // JsonNode actual = evaluate("1", json, JsonNode.class); // Does not work
+ // JsonNode actual = evaluate("'1'", json, JsonNode.class); // Does not work
+ JsonNode actual = evaluate(json, "['1']", JsonNode.class);
+ assertEquals(4, actual.asInt());
+ }
+
+ @Test
+ public void testNestedArrayConstruct() throws Exception {
+ Object json = mapper.readTree("[[3], [4, 5], []]");
+ // JsonNode actual = evaluate("1.1", json, JsonNode.class); // Does not work
+ // JsonNode actual = evaluate("[1][1]", json, JsonNode.class); // Does not work
+ JsonNode actual = evaluate(json, "['1']['1']", JsonNode.class);
+ assertEquals(5, actual.asInt());
+ }
+
+ @Test
+ public void testNestedHashConstruct() throws Exception {
+ Object json = mapper.readTree("{\"foo\": {\"bar\": 4, \"fizz\": 5} }");
+ JsonNode actual = evaluate(json, "foo.fizz", JsonNode.class);
+ assertEquals(5, actual.asInt());
+ }
+
+ @Test
+ public void testImplicitStringConversion() throws Exception {
+ String json = "{\"foo\": {\"bar\": 4, \"fizz\": 5} }";
+ JsonNode actual = evaluate(json, "foo.fizz", JsonNode.class);
+ assertEquals(5, actual.asInt());
+ }
+
+ @Test(expected = SpelEvaluationException.class)
+ public void testUnsupportedString() throws Exception {
+ String xml = "?";
+ evaluate(xml, "what", Object.class);
+ }
+
+ @Test(expected = SpelEvaluationException.class)
+ public void testUnsupportedJson() throws Exception {
+ String json = "\"literal\"";
+ evaluate(json, "foo", Object.class);
+ }
+
+ private T evaluate(Object target, String expression, Class expectedType) {
+ return parser.parseExpression(expression).getValue(context, target, expectedType);
+ }
+
+}