GH-476 Add generic JsonMessageConverter compatible with Jackson and Gson
Added implementation of JsonMessageConverter which is initialized with JsonMapper so it can delegate to Jackson or Gson based on property setting and/or availability of the underlying library. Resolves #476
This commit is contained in:
@@ -27,8 +27,6 @@ import java.util.stream.Collectors;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
@@ -40,11 +38,11 @@ import org.springframework.cloud.function.context.catalog.BeanFactoryAwareFuncti
|
||||
import org.springframework.cloud.function.context.catalog.FunctionInspector;
|
||||
import org.springframework.cloud.function.json.GsonMapper;
|
||||
import org.springframework.cloud.function.json.JacksonMapper;
|
||||
import org.springframework.cloud.function.json.JsonMapper;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
import org.springframework.context.annotation.ComponentScan.Filter;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.FilterType;
|
||||
import org.springframework.core.convert.converter.GenericConverter;
|
||||
@@ -53,7 +51,6 @@ import org.springframework.lang.Nullable;
|
||||
import org.springframework.messaging.converter.AbstractMessageConverter;
|
||||
import org.springframework.messaging.converter.ByteArrayMessageConverter;
|
||||
import org.springframework.messaging.converter.CompositeMessageConverter;
|
||||
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
|
||||
import org.springframework.messaging.converter.MessageConverter;
|
||||
import org.springframework.messaging.converter.StringMessageConverter;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
@@ -74,8 +71,7 @@ public class ContextFunctionCatalogAutoConfiguration {
|
||||
static final String PREFERRED_MAPPER_PROPERTY = "spring.http.converters.preferred-json-mapper";
|
||||
|
||||
@Bean
|
||||
public FunctionRegistry functionCatalog(List<MessageConverter> messageConverters, @Nullable ObjectMapper objectMapper,
|
||||
ConfigurableApplicationContext context) {
|
||||
public FunctionRegistry functionCatalog(List<MessageConverter> messageConverters, JsonMapper jsonMapper, ConfigurableApplicationContext context) {
|
||||
ConfigurableConversionService conversionService = (ConfigurableConversionService) context.getBeanFactory().getConversionService();
|
||||
Map<String, GenericConverter> converters = context.getBeansOfType(GenericConverter.class);
|
||||
for (GenericConverter converter : converters.values()) {
|
||||
@@ -104,10 +100,7 @@ public class ContextFunctionCatalogAutoConfiguration {
|
||||
: converter;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
MappingJackson2MessageConverter jsonConverter = new MappingJackson2MessageConverter();
|
||||
jsonConverter.setObjectMapper(objectMapper == null ? new ObjectMapper() : objectMapper);
|
||||
mcList.add(NegotiatingMessageConverterWrapper.wrap(jsonConverter));
|
||||
mcList.add(NegotiatingMessageConverterWrapper.wrap(new JsonMessageConverter(jsonMapper)));
|
||||
mcList.add(NegotiatingMessageConverterWrapper.wrap(new ByteArrayMessageConverter()));
|
||||
mcList.add(NegotiatingMessageConverterWrapper.wrap(new StringMessageConverter()));
|
||||
|
||||
@@ -144,32 +137,16 @@ public class ContextFunctionCatalogAutoConfiguration {
|
||||
|
||||
}
|
||||
|
||||
private static class PreferGsonOrMissingJacksonCondition extends AnyNestedCondition {
|
||||
|
||||
PreferGsonOrMissingJacksonCondition() {
|
||||
super(ConfigurationPhase.REGISTER_BEAN);
|
||||
}
|
||||
|
||||
@ConditionalOnProperty(name = PREFERRED_MAPPER_PROPERTY, havingValue = "gson", matchIfMissing = false)
|
||||
static class GsonPreferred {
|
||||
|
||||
}
|
||||
|
||||
@ConditionalOnMissingBean(ObjectMapper.class)
|
||||
static class JacksonMissing {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(Gson.class)
|
||||
@ConditionalOnBean(Gson.class)
|
||||
@Conditional(PreferGsonOrMissingJacksonCondition.class)
|
||||
@ConditionalOnProperty(name = PREFERRED_MAPPER_PROPERTY, havingValue = "gson", matchIfMissing = false)
|
||||
protected static class GsonConfiguration {
|
||||
|
||||
@Bean
|
||||
public GsonMapper jsonMapper(Gson gson) {
|
||||
public GsonMapper jsonMapper(@Nullable Gson gson) {
|
||||
if (gson == null) {
|
||||
gson = new Gson();
|
||||
}
|
||||
return new GsonMapper(gson);
|
||||
}
|
||||
|
||||
@@ -177,13 +154,14 @@ public class ContextFunctionCatalogAutoConfiguration {
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(ObjectMapper.class)
|
||||
@ConditionalOnBean(ObjectMapper.class)
|
||||
@ConditionalOnProperty(name = ContextFunctionCatalogAutoConfiguration.PREFERRED_MAPPER_PROPERTY, //
|
||||
havingValue = "jackson", matchIfMissing = true)
|
||||
@ConditionalOnProperty(name = PREFERRED_MAPPER_PROPERTY, havingValue = "jackson", matchIfMissing = true)
|
||||
protected static class JacksonConfiguration {
|
||||
|
||||
@Bean
|
||||
public JacksonMapper jsonMapper(ObjectMapper mapper) {
|
||||
public JacksonMapper jsonMapper(@Nullable ObjectMapper mapper) {
|
||||
if (mapper == null) {
|
||||
mapper = new ObjectMapper();
|
||||
}
|
||||
return new JacksonMapper(mapper);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2020-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cloud.function.context.config;
|
||||
|
||||
import org.springframework.cloud.function.json.JsonMapper;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.messaging.converter.AbstractMessageConverter;
|
||||
import org.springframework.messaging.converter.MessageConverter;
|
||||
import org.springframework.util.MimeType;
|
||||
|
||||
/**
|
||||
* Implementation of {@link MessageConverter} which uses Jackson or Gson libraries to do the
|
||||
* actual conversion via {@link JsonMapper} instance.
|
||||
*
|
||||
* @author Oleg Zhurakousky
|
||||
* @since 3.0.4
|
||||
*/
|
||||
class JsonMessageConverter extends AbstractMessageConverter {
|
||||
|
||||
private final JsonMapper jsonMapper;
|
||||
|
||||
JsonMessageConverter(JsonMapper jsonMapper) {
|
||||
this(jsonMapper, new MimeType("application", "json"));
|
||||
}
|
||||
|
||||
JsonMessageConverter(JsonMapper jsonMapper, MimeType... supportedMimeTypes) {
|
||||
super(supportedMimeTypes);
|
||||
this.jsonMapper = jsonMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean supports(Class<?> clazz) {
|
||||
// should not be called, since we override canConvertFrom/canConvertTo instead
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canConvertTo(Object payload, @Nullable MessageHeaders headers) {
|
||||
if (!supportsMimeType(headers)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canConvertFrom(Message<?> message, @Nullable Class<?> targetClass) {
|
||||
if (targetClass == null || !supportsMimeType(message.getHeaders())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object convertFromInternal(Message<?> message, Class<?> targetClass, @Nullable Object conversionHint) {
|
||||
if (targetClass.isInstance(message.getPayload())) {
|
||||
return message.getPayload();
|
||||
}
|
||||
Object result = jsonMapper.fromJson(message.getPayload(), targetClass);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object convertToInternal(Object payload, @Nullable MessageHeaders headers,
|
||||
@Nullable Object conversionHint) {
|
||||
return jsonMapper.toJson(payload);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,14 +16,16 @@
|
||||
|
||||
package org.springframework.cloud.function.json;
|
||||
|
||||
import java.io.Reader;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
* @author Oleg Zhurakousky
|
||||
*
|
||||
*/
|
||||
public class GsonMapper implements JsonMapper {
|
||||
|
||||
@@ -35,7 +37,7 @@ public class GsonMapper implements JsonMapper {
|
||||
|
||||
@Override
|
||||
public <T> T toObject(String json, Type type) {
|
||||
return this.gson.fromJson(json, type);
|
||||
return this.fromJson(json, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -43,4 +45,27 @@ public class GsonMapper implements JsonMapper {
|
||||
return this.gson.toJson(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T fromJson(Object json, Type type) {
|
||||
T convertedValue = null;
|
||||
if (json instanceof byte[]) {
|
||||
convertedValue = this.gson.fromJson(new String(((byte[]) json), StandardCharsets.UTF_8), type);
|
||||
}
|
||||
else if (json instanceof String) {
|
||||
convertedValue = this.gson.fromJson((String) json, type);
|
||||
}
|
||||
else if (json instanceof Reader) {
|
||||
convertedValue = this.gson.fromJson((Reader) json, type);
|
||||
}
|
||||
else if (json instanceof JsonElement) {
|
||||
convertedValue = this.gson.fromJson((JsonElement) json, type);
|
||||
}
|
||||
return convertedValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toJson(Object value) {
|
||||
return this.gson.toJson(value).getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,9 +16,11 @@
|
||||
|
||||
package org.springframework.cloud.function.json;
|
||||
|
||||
import java.io.Reader;
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JavaType;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.type.TypeFactory;
|
||||
|
||||
@@ -36,13 +38,40 @@ public class JacksonMapper implements JsonMapper {
|
||||
|
||||
@Override
|
||||
public <T> T toObject(String json, Type type) {
|
||||
return this.fromJson(json, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T fromJson(Object json, Type type) {
|
||||
T convertedValue = null;
|
||||
JavaType constructType = TypeFactory.defaultInstance().constructType(type);
|
||||
|
||||
try {
|
||||
return this.mapper.readValue(json,
|
||||
TypeFactory.defaultInstance().constructType(type));
|
||||
if (json instanceof String) {
|
||||
convertedValue = this.mapper.readValue((String) json, constructType);
|
||||
}
|
||||
else if (json instanceof byte[]) {
|
||||
convertedValue = this.mapper.readValue((byte[]) json, constructType);
|
||||
}
|
||||
else if (json instanceof Reader) {
|
||||
convertedValue = this.mapper.readValue((Reader) json, constructType);
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new IllegalArgumentException("Cannot convert JSON " + json, e);
|
||||
//ignore and let other converters have a chance
|
||||
}
|
||||
return convertedValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toJson(Object value) {
|
||||
try {
|
||||
return this.mapper.writeValueAsBytes(value);
|
||||
}
|
||||
catch (Exception e) {
|
||||
//ignore and let other converters have a chance
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -55,4 +84,6 @@ public class JacksonMapper implements JsonMapper {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -50,9 +50,15 @@ public interface JsonMapper {
|
||||
* @param type type
|
||||
* @return object
|
||||
* @since 2.0
|
||||
* @deprecated since v3.0.4 in favor of {@link #fromJson(Object, Type)}
|
||||
*/
|
||||
@Deprecated
|
||||
<T> T toObject(String json, Type type);
|
||||
|
||||
<T> T fromJson(Object json, Type type);
|
||||
|
||||
byte[] toJson(Object value);
|
||||
|
||||
/**
|
||||
* @param <T> type for list arguments
|
||||
* @param json JSON input
|
||||
@@ -60,6 +66,7 @@ public interface JsonMapper {
|
||||
* @return single object
|
||||
* @deprecated since v2.0 in favor of {@link #toObject(String, Type)}
|
||||
*/
|
||||
@Deprecated
|
||||
default <T> T toSingle(String json, Class<T> type) {
|
||||
return toObject(json, type);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user