Support generics in the JsonToObjectTransformer (#2819)

* Support generics in the `JsonToObjectTransformer`

* Deprecate `JsonObjectMapperAdapter` if favor of `default` methods in
the `JsonObjectMapper`
* Introduce `JsonObjectMapper.fromJson(Object, ResolvableType)` to
support generics during deserialization
* Add `JsonHeaders.RESOLVABLE_TYPE` header handling for the
`ResolvableType` management
* Add `ResolvableType` argument into the `JsonToObjectTransformer`
* Change the `JsonToObjectTransformer` logic to consult request message
headers first
* Add `ResolvableType`-based factory method into the `Transformers`
* Document the change

* * Use `0` for conversion to array for headers to remove
in the `JsonToObjectTransformer`
This commit is contained in:
Artem Bilan
2019-03-18 17:15:32 -04:00
committed by Gary Russell
parent 8f86ef918c
commit 5421574ef5
13 changed files with 285 additions and 107 deletions

View File

@@ -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 java.util.function.Function;
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 @@ public abstract class Transformers {
}
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 abstract class Transformers {
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);
}

View File

@@ -20,12 +20,16 @@ import java.io.IOException;
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 @@ import org.springframework.messaging.Message;
*
* @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));
}
public JsonToObjectTransformer(JsonObjectMapper<?, ?> jsonObjectMapper) {
this(null, jsonObjectMapper);
/**
* 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(Class<?> targetClass, JsonObjectMapper<?, ?> jsonObjectMapper) {
this.targetClass = targetClass;
public JsonToObjectTransformer(@Nullable JsonObjectMapper<?, ?> jsonObjectMapper) {
this((Class<?>) null, jsonObjectMapper);
}
public JsonToObjectTransformer(@Nullable Class<?> targetClass, @Nullable JsonObjectMapper<?, ?> jsonObjectMapper) {
this(ResolvableType.forClass(targetClass), jsonObjectMapper);
}
/**
* 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 class JsonToObjectTransformer extends AbstractTransformer implements Bean
@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<Object> 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);
}
}
}
}

View File

@@ -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 @@ public final class 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<String> 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));
}

View File

@@ -27,6 +27,7 @@ import java.util.Collection;
import java.util.Map;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.core.ResolvableType;
import org.springframework.util.ClassUtils;
/**
@@ -40,11 +41,10 @@ import org.springframework.util.ClassUtils;
*
* @since 3.0
*/
public abstract class AbstractJacksonJsonObjectMapper<N, P, J> extends JsonObjectMapperAdapter<N, P>
implements BeanClassLoaderAware {
public abstract class AbstractJacksonJsonObjectMapper<N, P, J> implements JsonObjectMapper<N, P>, BeanClassLoaderAware {
protected static final Collection<Class<?>> supportedJsonTypes =
Arrays.<Class<?>>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 @@ public abstract class AbstractJacksonJsonObjectMapper<N, P, J> extends JsonObjec
@Override
public <T> T fromJson(Object json, Class<T> valueType) throws IOException {
return fromJson(json, this.constructType(valueType));
return fromJson(json, constructType(valueType));
}
@Override
public <T> T fromJson(Object json, ResolvableType valueType) throws IOException {
return fromJson(json, constructType(valueType.getType()));
}
@Override
public <T> T fromJson(Object json, Map<String, Object> javaTypes) throws IOException {
J javaType = extractJavaType(javaTypes);
return this.fromJson(json, javaType);
return fromJson(json, javaType);
}
protected J createJavaType(Map<String, Object> javaTypes, String javaTypeKey) {

View File

@@ -52,13 +52,12 @@ import org.springframework.util.ClassUtils;
* @deprecated since 5.2. Will be removed in the next version.
*/
@Deprecated
public class BoonJsonObjectMapper extends JsonObjectMapperAdapter<Map<String, Object>, Object>
implements BeanClassLoaderAware {
public class BoonJsonObjectMapper implements JsonObjectMapper<Map<String, Object>, Object>, BeanClassLoaderAware {
private static final Log logger = LogFactory.getLog(BoonJsonObjectMapper.class);
private static final Collection<Class<?>> supportedJsonTypes =
Arrays.<Class<?>>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;

View File

@@ -19,8 +19,14 @@ 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.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 @@ import java.util.Map;
* @param <P> - The expected type of JSON Parser.
*
* @author Artem Bilan
*
* @since 3.0
*
*/
public interface JsonObjectMapper<N, P> {
String toJson(Object value) throws IOException;
default String toJson(Object value) throws IOException {
return null;
}
void toJson(Object value, Writer writer) throws IOException;
default void toJson(Object value, Writer writer) throws IOException {
N toJsonNode(Object value) throws IOException;
}
<T> T fromJson(Object json, Class<T> valueType) throws IOException;
default N toJsonNode(Object value) throws IOException {
return null;
}
<T> T fromJson(Object json, Map<String, Object> javaTypes) throws IOException;
default <T> T fromJson(Object json, Class<T> valueType) throws IOException {
return null;
}
<T> T fromJson(P parser, Type valueType) throws IOException;
/**
* Deserialize a JSON to an expected {@link ResolvableType}.
* @param json the JSON to deserialize
* @param valueType the {@link ResolvableType} for the target object.
* @param <T> the expected object type
* @return deserialization result object
* @throws IOException a JSON parsing exception
* @since 5.2
*/
default <T> T fromJson(Object json, ResolvableType valueType) throws IOException {
return null;
}
default <T> T fromJson(Object json, Map<String, Object> javaTypes) throws IOException {
return null;
}
default <T> T fromJson(P parser, Type valueType) throws IOException {
return null;
}
default void populateJavaTypes(Map<String, Object> 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);
}
}
map.put(JsonHeaders.RESOLVABLE_TYPE, buildResolvableType(targetClass, contentClass, keyClass));
}
static ResolvableType buildResolvableType(Class<?> targetClass, @Nullable Class<?> contentClass,
@Nullable Class<?> keyClass) {
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<String, Object> map, Object object);
}

View File

@@ -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<N, P> implements JsonObjectMapper<N, P> {
@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> T fromJson(Object json, Class<T> valueType) throws IOException {
return null;
}
@Override
public <T> T fromJson(P parser, Type valueType) throws IOException {
return null;
}
@Override
public <T> T fromJson(Object json, Map<String, Object> javaTypes) throws IOException {
return null;
}
@Override
public void populateJavaTypes(Map<String, Object> 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);
}
}
}

View File

@@ -23,11 +23,11 @@ import org.junit.runner.RunWith;
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 class JsonToObjectTransformerParserTests {
assertThat(result.getJson()).isEqualTo(jsonString);
}
@SuppressWarnings("rawtypes")
static class CustomJsonObjectMapper extends JsonObjectMapperAdapter {
static class CustomJsonObjectMapper implements JsonObjectMapper<Object, Object> {
@Override
public Object fromJson(Object json, Class valueType) {
return new TestJsonContainer((String) json);
@SuppressWarnings("unchecked")
public <T> T fromJson(Object json, Class<T> valueType) {
return (T) new TestJsonContainer((String) json);
}
@Override
@SuppressWarnings("unchecked")
public <T> T fromJson(Object json, ResolvableType valueType) {
return (T) new TestJsonContainer((String) json);
}
}

View File

@@ -18,8 +18,12 @@ package org.springframework.integration.json;
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 @@ import com.fasterxml.jackson.databind.ObjectMapper;
* @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<List<TestPerson>>() { }));
// 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

View File

@@ -33,7 +33,7 @@ import org.springframework.integration.channel.QueueChannel;
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 class ObjectToJsonTransformerParserTests {
assertThat(expression.getValue(evaluationContext, payload, Boolean.class)).isTrue();
}
static class CustomJsonObjectMapper extends JsonObjectMapperAdapter<Object, Object> {
static class CustomJsonObjectMapper implements JsonObjectMapper<Object, Object> {
@Override
public String toJson(Object value) {

View File

@@ -183,11 +183,11 @@ public class ObjectToJsonTransformerTests {
List<String> 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<String, String> 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);
}

View File

@@ -449,6 +449,10 @@ See <<spel-property-accessors>> 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

View File

@@ -16,6 +16,9 @@ See <<rate-limiter-advice>> for more information.
[[x5.2-general]]
=== General Changes
The `JsonToObjectTransformer` now supports generics for the target object to deserialize into.
See <<json-transformers>> for more information.
[[x5.2-amqp]]
==== AMQP Changes