diff --git a/spring-integration-core/src/main/java/org/springframework/integration/dsl/Transformers.java b/spring-integration-core/src/main/java/org/springframework/integration/dsl/Transformers.java index 5166792e313..47eea7a3273 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/dsl/Transformers.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/dsl/Transformers.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 the original author or authors. + * Copyright 2016-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. @@ -21,6 +21,7 @@ import org.reactivestreams.Publisher; +import org.springframework.core.ResolvableType; import org.springframework.core.convert.converter.Converter; import org.springframework.core.serializer.Deserializer; import org.springframework.core.serializer.Serializer; @@ -161,15 +162,25 @@ else if (resultType != null) { } public static JsonToObjectTransformer fromJson() { - return fromJson(null, null); + return fromJson((Class) null, null); } public static JsonToObjectTransformer fromJson(@Nullable Class targetClass) { return fromJson(targetClass, null); } + /** + * Construct a {@link JsonToObjectTransformer} based on the provided {@link ResolvableType}. + * @param targetType the {@link ResolvableType} top use. + * @return the {@link JsonToObjectTransformer} instance. + * @since 5.2 + */ + public static JsonToObjectTransformer fromJson(ResolvableType targetType) { + return fromJson(targetType, null); + } + public static JsonToObjectTransformer fromJson(@Nullable JsonObjectMapper jsonObjectMapper) { - return fromJson(null, jsonObjectMapper); + return fromJson((Class) null, jsonObjectMapper); } public static JsonToObjectTransformer fromJson(@Nullable Class targetClass, @@ -178,6 +189,20 @@ public static JsonToObjectTransformer fromJson(@Nullable Class targetClass, return new JsonToObjectTransformer(targetClass, jsonObjectMapper); } + /** + * Construct a {@link JsonToObjectTransformer} based on the provided {@link ResolvableType} + * and {@link JsonObjectMapper}. + * @param targetType the {@link ResolvableType} top use. + * @param jsonObjectMapper the {@link JsonObjectMapper} top use. + * @return the {@link JsonToObjectTransformer} instance. + * @since 5.2 + */ + public static JsonToObjectTransformer fromJson(ResolvableType targetType, + @Nullable JsonObjectMapper jsonObjectMapper) { + + return new JsonToObjectTransformer(targetType, jsonObjectMapper); + } + public static PayloadSerializingTransformer serializer() { return serializer(null); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/json/JsonToObjectTransformer.java b/spring-integration-core/src/main/java/org/springframework/integration/json/JsonToObjectTransformer.java index 328be2db8c9..1c358ec9e6b 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/json/JsonToObjectTransformer.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/json/JsonToObjectTransformer.java @@ -20,12 +20,16 @@ import java.io.UncheckedIOException; import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.core.ResolvableType; import org.springframework.integration.mapping.support.JsonHeaders; -import org.springframework.integration.support.AbstractIntegrationMessageBuilder; import org.springframework.integration.support.json.JsonObjectMapper; import org.springframework.integration.support.json.JsonObjectMapperProvider; import org.springframework.integration.transformer.AbstractTransformer; +import org.springframework.lang.Nullable; import org.springframework.messaging.Message; +import org.springframework.messaging.MessageHeaders; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; /** * Transformer implementation that converts a JSON string payload into an instance of the @@ -42,35 +46,60 @@ * * @author Mark Fisher * @author Artem Bilan + * * @see JsonObjectMapper * @see org.springframework.integration.support.json.JsonObjectMapperProvider + * * @since 2.0 */ public class JsonToObjectTransformer extends AbstractTransformer implements BeanClassLoaderAware { - private final Class targetClass; + private final ResolvableType targetType; private final JsonObjectMapper jsonObjectMapper; + private ClassLoader classLoader; + public JsonToObjectTransformer() { this((Class) null); } - public JsonToObjectTransformer(Class targetClass) { - this(targetClass, null); + public JsonToObjectTransformer(@Nullable Class targetClass) { + this(ResolvableType.forClass(targetClass)); + } + + /** + * Construct an instance based on the provided {@link ResolvableType}. + * @param targetType the {@link ResolvableType} to use. + * @since 5.2 + */ + public JsonToObjectTransformer(ResolvableType targetType) { + this(targetType, null); + } + + public JsonToObjectTransformer(@Nullable JsonObjectMapper jsonObjectMapper) { + this((Class) null, jsonObjectMapper); } - public JsonToObjectTransformer(JsonObjectMapper jsonObjectMapper) { - this(null, jsonObjectMapper); + public JsonToObjectTransformer(@Nullable Class targetClass, @Nullable JsonObjectMapper jsonObjectMapper) { + this(ResolvableType.forClass(targetClass), jsonObjectMapper); } - public JsonToObjectTransformer(Class targetClass, JsonObjectMapper jsonObjectMapper) { - this.targetClass = targetClass; + /** + * Construct an instance based on the provided {@link ResolvableType} and {@link JsonObjectMapper}. + * @param targetType the {@link ResolvableType} to use. + * @param jsonObjectMapper the {@link JsonObjectMapper} to use. + * @since 5.2 + */ + public JsonToObjectTransformer(ResolvableType targetType, @Nullable JsonObjectMapper jsonObjectMapper) { + Assert.notNull(targetType, "'targetType' must not be null"); + this.targetType = targetType; this.jsonObjectMapper = (jsonObjectMapper != null) ? jsonObjectMapper : JsonObjectMapperProvider.newInstance(); } @Override public void setBeanClassLoader(ClassLoader classLoader) { + this.classLoader = classLoader; if (this.jsonObjectMapper instanceof BeanClassLoaderAware) { ((BeanClassLoaderAware) this.jsonObjectMapper).setBeanClassLoader(classLoader); } @@ -83,21 +112,72 @@ public String getComponentType() { @Override protected Object doTransform(Message message) { + MessageHeaders headers = message.getHeaders(); + boolean removeHeaders = false; + ResolvableType valueType = obtainResolvableTypeFromHeadersIfAny(headers); + + if (valueType != null) { + removeHeaders = true; + } + else { + valueType = this.targetType; + } + + Object result; try { - if (this.targetClass != null) { - return this.jsonObjectMapper.fromJson(message.getPayload(), this.targetClass); - } - else { - Object result = this.jsonObjectMapper.fromJson(message.getPayload(), message.getHeaders()); - AbstractIntegrationMessageBuilder messageBuilder = this.getMessageBuilderFactory().withPayload(result) - .copyHeaders(message.getHeaders()) - .removeHeaders(JsonHeaders.HEADERS.toArray(new String[3])); - return messageBuilder.build(); - } + result = this.jsonObjectMapper.fromJson(message.getPayload(), valueType); + } catch (IOException e) { throw new UncheckedIOException(e); } + + if (removeHeaders) { + return getMessageBuilderFactory() + .withPayload(result) + .copyHeaders(headers) + .removeHeaders(JsonHeaders.HEADERS.toArray(new String[0])) + .build(); + } + else { + return result; + } + } + + + private ResolvableType obtainResolvableTypeFromHeadersIfAny(MessageHeaders headers) { + ResolvableType valueType = null; + if (headers.containsKey(JsonHeaders.TYPE_ID) || headers.containsKey(JsonHeaders.RESOLVABLE_TYPE)) { + valueType = headers.get(JsonHeaders.RESOLVABLE_TYPE, ResolvableType.class); + if (valueType == null) { + Class targetClass = getClassForValue(headers.get(JsonHeaders.TYPE_ID)); + Class contentClass = null; + Class keyClass = null; + if (headers.containsKey(JsonHeaders.CONTENT_TYPE_ID)) { + contentClass = getClassForValue(headers.get(JsonHeaders.CONTENT_TYPE_ID)); + } + if (headers.containsKey(JsonHeaders.KEY_TYPE_ID)) { + keyClass = getClassForValue(headers.get(JsonHeaders.KEY_TYPE_ID)); + } + + valueType = JsonObjectMapper.buildResolvableType(targetClass, contentClass, keyClass); + } + } + return valueType; + } + + private Class getClassForValue(Object classValue) { + if (classValue instanceof Class) { + return (Class) classValue; + } + else { + try { + return ClassUtils.forName(classValue.toString(), this.classLoader); + } + catch (ClassNotFoundException | LinkageError e) { + throw new IllegalStateException(e); + } + } } } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/mapping/support/JsonHeaders.java b/spring-integration-core/src/main/java/org/springframework/integration/mapping/support/JsonHeaders.java index 523e580b51b..070fef27847 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/mapping/support/JsonHeaders.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/mapping/support/JsonHeaders.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 the original author or authors. + * Copyright 2013-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. @@ -42,7 +42,14 @@ private JsonHeaders() { public static final String KEY_TYPE_ID = PREFIX + "__KeyTypeId__"; + /** + * The header to represent a {@link org.springframework.core.ResolvableType} + * for the target deserialized object. + * @since 5.2 + */ + public static final String RESOLVABLE_TYPE = PREFIX + "_resolvableType"; + public static final Collection HEADERS = - Collections.unmodifiableList(Arrays.asList(TYPE_ID, CONTENT_TYPE_ID, KEY_TYPE_ID)); + Collections.unmodifiableList(Arrays.asList(TYPE_ID, CONTENT_TYPE_ID, KEY_TYPE_ID, RESOLVABLE_TYPE)); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/support/json/AbstractJacksonJsonObjectMapper.java b/spring-integration-core/src/main/java/org/springframework/integration/support/json/AbstractJacksonJsonObjectMapper.java index 7f56c775155..2f0ff1132f4 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/support/json/AbstractJacksonJsonObjectMapper.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/support/json/AbstractJacksonJsonObjectMapper.java @@ -27,6 +27,7 @@ import java.util.Map; import org.springframework.beans.factory.BeanClassLoaderAware; +import org.springframework.core.ResolvableType; import org.springframework.util.ClassUtils; /** @@ -40,11 +41,10 @@ * * @since 3.0 */ -public abstract class AbstractJacksonJsonObjectMapper extends JsonObjectMapperAdapter - implements BeanClassLoaderAware { +public abstract class AbstractJacksonJsonObjectMapper implements JsonObjectMapper, BeanClassLoaderAware { protected static final Collection> supportedJsonTypes = - Arrays.>asList(String.class, byte[].class, File.class, URL.class, InputStream.class, Reader.class); + Arrays.asList(String.class, byte[].class, File.class, URL.class, InputStream.class, Reader.class); private volatile ClassLoader classLoader = ClassUtils.getDefaultClassLoader(); @@ -59,13 +59,18 @@ protected ClassLoader getClassLoader() { @Override public T fromJson(Object json, Class valueType) throws IOException { - return fromJson(json, this.constructType(valueType)); + return fromJson(json, constructType(valueType)); + } + + @Override + public T fromJson(Object json, ResolvableType valueType) throws IOException { + return fromJson(json, constructType(valueType.getType())); } @Override public T fromJson(Object json, Map javaTypes) throws IOException { J javaType = extractJavaType(javaTypes); - return this.fromJson(json, javaType); + return fromJson(json, javaType); } protected J createJavaType(Map javaTypes, String javaTypeKey) { diff --git a/spring-integration-core/src/main/java/org/springframework/integration/support/json/BoonJsonObjectMapper.java b/spring-integration-core/src/main/java/org/springframework/integration/support/json/BoonJsonObjectMapper.java index 82862ad7a74..c70aebb8a97 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/support/json/BoonJsonObjectMapper.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/support/json/BoonJsonObjectMapper.java @@ -52,13 +52,12 @@ * @deprecated since 5.2. Will be removed in the next version. */ @Deprecated -public class BoonJsonObjectMapper extends JsonObjectMapperAdapter, Object> - implements BeanClassLoaderAware { +public class BoonJsonObjectMapper implements JsonObjectMapper, Object>, BeanClassLoaderAware { private static final Log logger = LogFactory.getLog(BoonJsonObjectMapper.class); private static final Collection> supportedJsonTypes = - Arrays.>asList(String.class, byte[].class, byte[].class, File.class, InputStream.class, Reader.class); + Arrays.asList(String.class, byte[].class, byte[].class, File.class, InputStream.class, Reader.class); private final ObjectMapper objectMapper; diff --git a/spring-integration-core/src/main/java/org/springframework/integration/support/json/JsonObjectMapper.java b/spring-integration-core/src/main/java/org/springframework/integration/support/json/JsonObjectMapper.java index 9824d4da683..d5fc569d209 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/support/json/JsonObjectMapper.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/support/json/JsonObjectMapper.java @@ -19,8 +19,14 @@ import java.io.IOException; import java.io.Writer; import java.lang.reflect.Type; +import java.util.Collection; import java.util.Map; +import org.springframework.core.ResolvableType; +import org.springframework.core.convert.TypeDescriptor; +import org.springframework.integration.mapping.support.JsonHeaders; +import org.springframework.lang.Nullable; + /** * Strategy interface to convert an Object to/from the JSON representation. * @@ -28,22 +34,97 @@ * @param

- The expected type of JSON Parser. * * @author Artem Bilan + * * @since 3.0 * */ public interface JsonObjectMapper { - String toJson(Object value) throws IOException; + default String toJson(Object value) throws IOException { + return null; + } + + default void toJson(Object value, Writer writer) throws IOException { + + } + + default N toJsonNode(Object value) throws IOException { + return null; + } + + default T fromJson(Object json, Class valueType) throws IOException { + return null; + } + + /** + * Deserialize a JSON to an expected {@link ResolvableType}. + * @param json the JSON to deserialize + * @param valueType the {@link ResolvableType} for the target object. + * @param the expected object type + * @return deserialization result object + * @throws IOException a JSON parsing exception + * @since 5.2 + */ + default T fromJson(Object json, ResolvableType valueType) throws IOException { + return null; + } + + default T fromJson(Object json, Map javaTypes) throws IOException { + return null; + } + + default T fromJson(P parser, Type valueType) throws IOException { + return null; + } - void toJson(Object value, Writer writer) throws IOException; + default void populateJavaTypes(Map map, Object object) { + Class targetClass = object.getClass(); + Class contentClass = null; + Class keyClass = null; + map.put(JsonHeaders.TYPE_ID, targetClass); + if (object instanceof Collection && !((Collection) object).isEmpty()) { + Object firstElement = ((Collection) object).iterator().next(); + if (firstElement != null) { + contentClass = firstElement.getClass(); + map.put(JsonHeaders.CONTENT_TYPE_ID, contentClass); + } + } + if (object instanceof Map && !((Map) object).isEmpty()) { + Object firstValue = ((Map) object).values().iterator().next(); + if (firstValue != null) { + contentClass = firstValue.getClass(); + map.put(JsonHeaders.CONTENT_TYPE_ID, contentClass); + } + Object firstKey = ((Map) object).keySet().iterator().next(); + if (firstKey != null) { + keyClass = firstKey.getClass(); + map.put(JsonHeaders.KEY_TYPE_ID, keyClass); + } + } - N toJsonNode(Object value) throws IOException; - T fromJson(Object json, Class valueType) throws IOException; + map.put(JsonHeaders.RESOLVABLE_TYPE, buildResolvableType(targetClass, contentClass, keyClass)); + } - T fromJson(Object json, Map javaTypes) throws IOException; + static ResolvableType buildResolvableType(Class targetClass, @Nullable Class contentClass, + @Nullable Class keyClass) { - T fromJson(P parser, Type valueType) throws IOException; + if (keyClass != null) { + return TypeDescriptor + .map(targetClass, + TypeDescriptor.valueOf(keyClass), + TypeDescriptor.valueOf(contentClass)) + .getResolvableType(); + } + else if (contentClass != null) { + return TypeDescriptor + .collection(targetClass, + TypeDescriptor.valueOf(contentClass)) + .getResolvableType(); + } + else { + return ResolvableType.forClass(targetClass); + } + } - void populateJavaTypes(Map map, Object object); } diff --git a/spring-integration-core/src/main/java/org/springframework/integration/support/json/JsonObjectMapperAdapter.java b/spring-integration-core/src/main/java/org/springframework/integration/support/json/JsonObjectMapperAdapter.java index 53a00a31aef..6c967158e94 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/support/json/JsonObjectMapperAdapter.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/support/json/JsonObjectMapperAdapter.java @@ -16,66 +16,18 @@ package org.springframework.integration.support.json; -import java.io.IOException; -import java.io.Writer; -import java.lang.reflect.Type; -import java.util.Collection; -import java.util.Map; - -import org.springframework.integration.mapping.support.JsonHeaders; - /** * Simple {@linkplain JsonObjectMapper} adapter implementation, if there is no need * to provide entire operations implementation. * * @author Artem Bilan * @author Gary Russell + * * @since 3.0 + * + * @deprecated since 5.2 in favor of {@code default} methods in the {@link JsonObjectMapper} interface */ +@Deprecated public abstract class JsonObjectMapperAdapter implements JsonObjectMapper { - @Override - public String toJson(Object value) throws IOException { - return null; - } - - @Override - public void toJson(Object value, Writer writer) throws IOException { - } - - @Override - public N toJsonNode(Object value) throws IOException { - return null; - } - - @Override - public T fromJson(Object json, Class valueType) throws IOException { - return null; - } - - @Override - public T fromJson(P parser, Type valueType) throws IOException { - return null; - } - - @Override - public T fromJson(Object json, Map javaTypes) throws IOException { - return null; - } - - @Override - public void populateJavaTypes(Map map, Object object) { - map.put(JsonHeaders.TYPE_ID, object.getClass()); - if (object instanceof Collection && !((Collection) object).isEmpty()) { - Object firstElement = ((Collection) object).iterator().next(); - map.put(JsonHeaders.CONTENT_TYPE_ID, firstElement != null ? firstElement.getClass() : Object.class); - } - if (object instanceof Map && !((Map) object).isEmpty()) { - Object firstValue = ((Map) object).values().iterator().next(); - map.put(JsonHeaders.CONTENT_TYPE_ID, firstValue != null ? firstValue.getClass() : Object.class); - Object firstKey = ((Map) object).keySet().iterator().next(); - map.put(JsonHeaders.KEY_TYPE_ID, firstKey != null ? firstKey.getClass() : Object.class); - } - } - } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/json/JsonToObjectTransformerParserTests.java b/spring-integration-core/src/test/java/org/springframework/integration/json/JsonToObjectTransformerParserTests.java index 10d63627138..09afff4d148 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/json/JsonToObjectTransformerParserTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/json/JsonToObjectTransformerParserTests.java @@ -23,11 +23,11 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.core.ResolvableType; import org.springframework.integration.channel.QueueChannel; import org.springframework.integration.support.MessageBuilder; import org.springframework.integration.support.json.Jackson2JsonObjectMapper; import org.springframework.integration.support.json.JsonObjectMapper; -import org.springframework.integration.support.json.JsonObjectMapperAdapter; import org.springframework.integration.test.util.TestUtils; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; @@ -104,12 +104,18 @@ public void testInt2831CustomJsonObjectMapper() { assertThat(result.getJson()).isEqualTo(jsonString); } - @SuppressWarnings("rawtypes") - static class CustomJsonObjectMapper extends JsonObjectMapperAdapter { + static class CustomJsonObjectMapper implements JsonObjectMapper { @Override - public Object fromJson(Object json, Class valueType) { - return new TestJsonContainer((String) json); + @SuppressWarnings("unchecked") + public T fromJson(Object json, Class valueType) { + return (T) new TestJsonContainer((String) json); + } + + @Override + @SuppressWarnings("unchecked") + public T fromJson(Object json, ResolvableType valueType) { + return (T) new TestJsonContainer((String) json); } } diff --git a/spring-integration-core/src/test/java/org/springframework/integration/json/JsonToObjectTransformerTests.java b/spring-integration-core/src/test/java/org/springframework/integration/json/JsonToObjectTransformerTests.java index ac0fd827756..24c1e483eb6 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/json/JsonToObjectTransformerTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/json/JsonToObjectTransformerTests.java @@ -18,8 +18,12 @@ import static org.assertj.core.api.Assertions.assertThat; +import java.util.List; + import org.junit.Test; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.core.ResolvableType; import org.springframework.integration.support.json.Jackson2JsonObjectMapper; import org.springframework.messaging.Message; import org.springframework.messaging.support.GenericMessage; @@ -31,24 +35,36 @@ * @author Mark Fisher * @author Artem Bilan * @author Gary Russell + * * @since 2.0 */ public class JsonToObjectTransformerTests { @Test public void objectPayload() { - JsonToObjectTransformer transformer = new JsonToObjectTransformer(TestPerson.class); + JsonToObjectTransformer transformer = + new JsonToObjectTransformer( + ResolvableType.forType(new ParameterizedTypeReference>() { })); // Since DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES is disabled by default // (see Jackson2JsonObjectMapper) // the extra "foo" property is ignored. - String jsonString = "{\"firstName\":\"John\",\"lastName\":\"Doe\",\"age\":42," + - "\"address\":{\"number\":123,\"street\":\"Main Street\"}, \"foo\":\"bar\"}"; + String jsonString = "[{\"firstName\":\"John\",\"lastName\":\"Doe\",\"age\":42," + + "\"address\":{\"number\":123,\"street\":\"Main Street\"}, \"foo\":\"bar\"}]"; Message message = transformer.transform(new GenericMessage<>(jsonString)); - TestPerson person = (TestPerson) message.getPayload(); - assertThat(person.getFirstName()).isEqualTo("John"); - assertThat(person.getLastName()).isEqualTo("Doe"); - assertThat(person.getAge()).isEqualTo(42); - assertThat(person.getAddress().toString()).isEqualTo("123 Main Street"); + assertThat(message) + .extracting(Message::getPayload) + .isInstanceOf(List.class) + .asList() + .hasSize(1) + .element(0) + .isInstanceOf(TestPerson.class) + .satisfies((actual) -> { + TestPerson bean = (TestPerson) actual; + assertThat(bean).extracting(TestPerson::getFirstName).isEqualTo("John"); + assertThat(bean).extracting(TestPerson::getLastName).isEqualTo("Doe"); + assertThat(bean).extracting(TestPerson::getAge).isEqualTo(42); + assertThat(bean).extracting(TestPerson::getAddress).asString().isEqualTo("123 Main Street"); + }); } @Test diff --git a/spring-integration-core/src/test/java/org/springframework/integration/json/ObjectToJsonTransformerParserTests.java b/spring-integration-core/src/test/java/org/springframework/integration/json/ObjectToJsonTransformerParserTests.java index 61497388bf0..e05367eb09a 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/json/ObjectToJsonTransformerParserTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/json/ObjectToJsonTransformerParserTests.java @@ -33,7 +33,7 @@ import org.springframework.integration.support.DefaultMessageBuilderFactory; import org.springframework.integration.support.MessageBuilder; import org.springframework.integration.support.json.Jackson2JsonObjectMapper; -import org.springframework.integration.support.json.JsonObjectMapperAdapter; +import org.springframework.integration.support.json.JsonObjectMapper; import org.springframework.integration.test.util.TestUtils; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; @@ -174,7 +174,7 @@ public void testNodeResultType() { assertThat(expression.getValue(evaluationContext, payload, Boolean.class)).isTrue(); } - static class CustomJsonObjectMapper extends JsonObjectMapperAdapter { + static class CustomJsonObjectMapper implements JsonObjectMapper { @Override public String toJson(Object value) { diff --git a/spring-integration-core/src/test/java/org/springframework/integration/json/ObjectToJsonTransformerTests.java b/spring-integration-core/src/test/java/org/springframework/integration/json/ObjectToJsonTransformerTests.java index 7217662ae64..8022c8c2456 100644 --- a/spring-integration-core/src/test/java/org/springframework/integration/json/ObjectToJsonTransformerTests.java +++ b/spring-integration-core/src/test/java/org/springframework/integration/json/ObjectToJsonTransformerTests.java @@ -183,11 +183,11 @@ public void collectionOrMapWithNullFirstElement() { List list = Collections.singletonList(null); Message out = transformer.transform(new GenericMessage<>(list)); assertThat(out.getHeaders().get(JsonHeaders.TYPE_ID).toString()).contains("SingletonList"); - assertThat(out.getHeaders().get(JsonHeaders.CONTENT_TYPE_ID)).isEqualTo(Object.class); + assertThat(out.getHeaders()).doesNotContainKey(JsonHeaders.CONTENT_TYPE_ID); Map map = Collections.singletonMap("foo", null); out = transformer.transform(new GenericMessage<>(map)); assertThat(out.getHeaders().get(JsonHeaders.TYPE_ID).toString()).contains("SingletonMap"); - assertThat(out.getHeaders().get(JsonHeaders.CONTENT_TYPE_ID)).isEqualTo(Object.class); + assertThat(out.getHeaders()).doesNotContainKey(JsonHeaders.CONTENT_TYPE_ID); assertThat(out.getHeaders().get(JsonHeaders.KEY_TYPE_ID)).isEqualTo(String.class); } diff --git a/src/reference/asciidoc/transformer.adoc b/src/reference/asciidoc/transformer.adoc index 5c37e0f214f..f754d676001 100644 --- a/src/reference/asciidoc/transformer.adoc +++ b/src/reference/asciidoc/transformer.adoc @@ -449,6 +449,10 @@ See <> for more information. Beginning with version 5.1, the `resultType` can be configured as `BYTES` to produce a message with the `byte[]` payload for convenience when working with downstream handlers which operate with this data type. +Starting with version 5.2, the `JsonToObjectTransformer` can be configured with a `ResolvableType` to support generics during deserialization with the target JSON processor. +Also this component now consults request message headers first for the presence of the `JsonHeaders.RESOLVABLE_TYPE` or `JsonHeaders.TYPE_ID` and falls back to the configured type otherwise. +The `ObjectToJsonTransformer` now also populates a `JsonHeaders.RESOLVABLE_TYPE` header based on the request message payload for any possible downstream scenarios. + [[transformer-annotation]] ==== Configuring a Transformer with Annotations diff --git a/src/reference/asciidoc/whats-new.adoc b/src/reference/asciidoc/whats-new.adoc index d424efe1c80..23309e19cd9 100644 --- a/src/reference/asciidoc/whats-new.adoc +++ b/src/reference/asciidoc/whats-new.adoc @@ -16,6 +16,9 @@ See <> for more information. [[x5.2-general]] === General Changes +The `JsonToObjectTransformer` now supports generics for the target object to deserialize into. +See <> for more information. + [[x5.2-amqp]] ==== AMQP Changes