Skip to content

Support generics in the JsonToObjectTransformer #2819

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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;
Expand Down Expand Up @@ -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,
Expand All @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
}
Expand All @@ -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<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);
}
}
}

}
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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<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));

}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import java.util.Map;

import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.core.ResolvableType;
import org.springframework.util.ClassUtils;

/**
Expand All @@ -40,11 +41,10 @@
*
* @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();

Expand All @@ -59,13 +59,18 @@ protected ClassLoader getClassLoader() {

@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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,12 @@
* @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;
Expand Down
Loading