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:
committed by
Gary Russell
parent
8f86ef918c
commit
5421574ef5
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user