Minor Jackson encoder/decoder refactoring
Consolidate JsonView hint extraction in shared base class. Rename base class from AbstractJackson2Codec to Jackson2CodecSupport since the class mainly provides support methods.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 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.
|
||||
@@ -16,34 +16,46 @@
|
||||
|
||||
package org.springframework.http.codec.json;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.lang.reflect.TypeVariable;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonView;
|
||||
import com.fasterxml.jackson.databind.JavaType;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.type.TypeFactory;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.MimeType;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
|
||||
/**
|
||||
* Abstract base class for Jackson based decoder/encoder implementations.
|
||||
* Base class providing support methods for Jackson 2 encoding and decoding.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 5.0
|
||||
*/
|
||||
public class AbstractJackson2Codec {
|
||||
public abstract class Jackson2CodecSupport {
|
||||
|
||||
/**
|
||||
* Hint key to use with a {@link Class} value specifying the JSON View to use to serialize
|
||||
* or deserialize an object.
|
||||
* The key for the hint to specify a "JSON View" for encoding or decoding
|
||||
* with the value expected to be a {@link Class}.
|
||||
* @see <a href="http://wiki.fasterxml.com/JacksonJsonViews">Jackson JSON Views</a>
|
||||
*/
|
||||
public static final String JSON_VIEW_HINT = AbstractJackson2Codec.class.getName() + ".jsonView";
|
||||
public static final String JSON_VIEW_HINT = Jackson2CodecSupport.class.getName() + ".jsonView";
|
||||
|
||||
private static final String JSON_VIEW_HINT_ERROR =
|
||||
"@JsonView only supported for write hints with exactly 1 class argument: ";
|
||||
|
||||
protected static final List<MimeType> JSON_MIME_TYPES = Arrays.asList(
|
||||
new MimeType("application", "json", StandardCharsets.UTF_8),
|
||||
@@ -54,10 +66,9 @@ public class AbstractJackson2Codec {
|
||||
|
||||
|
||||
/**
|
||||
* Create a new Jackson codec for the given mapper.
|
||||
* @param mapper the Jackson ObjectMapper to use
|
||||
* Constructor with a Jackson {@link ObjectMapper} to use.
|
||||
*/
|
||||
protected AbstractJackson2Codec(ObjectMapper mapper) {
|
||||
protected Jackson2CodecSupport(ObjectMapper mapper) {
|
||||
Assert.notNull(mapper, "ObjectMapper must not be null");
|
||||
this.mapper = mapper;
|
||||
}
|
||||
@@ -141,4 +152,22 @@ public class AbstractJackson2Codec {
|
||||
return ResolvableType.NONE;
|
||||
}
|
||||
|
||||
protected Map<String, Object> getHints(ResolvableType actualType) {
|
||||
return getParameter(actualType)
|
||||
.flatMap(parameter -> Optional.ofNullable(getAnnotation(parameter, JsonView.class))
|
||||
.map(annotation -> {
|
||||
Class<?>[] classes = annotation.value();
|
||||
Assert.isTrue(classes.length == 1, JSON_VIEW_HINT_ERROR + parameter);
|
||||
return Collections.<String, Object>singletonMap(JSON_VIEW_HINT, classes[0]);
|
||||
}))
|
||||
.orElse(Collections.emptyMap());
|
||||
}
|
||||
|
||||
protected Optional<MethodParameter> getParameter(ResolvableType type) {
|
||||
return Optional.ofNullable (type.getSource() instanceof MethodParameter ?
|
||||
(MethodParameter) type.getSource() : null);
|
||||
}
|
||||
|
||||
protected abstract <A extends Annotation> A getAnnotation(MethodParameter parameter, Class<A> annotType);
|
||||
|
||||
}
|
||||
@@ -17,11 +17,10 @@
|
||||
package org.springframework.http.codec.json;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonView;
|
||||
import com.fasterxml.jackson.databind.JavaType;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.ObjectReader;
|
||||
@@ -49,11 +48,11 @@ import org.springframework.util.MimeType;
|
||||
* @since 5.0
|
||||
* @see Jackson2JsonEncoder
|
||||
*/
|
||||
public class Jackson2JsonDecoder extends AbstractJackson2Codec implements ServerHttpDecoder<Object> {
|
||||
public class Jackson2JsonDecoder extends Jackson2CodecSupport implements ServerHttpDecoder<Object> {
|
||||
|
||||
private final JsonObjectDecoder fluxObjectDecoder = new JsonObjectDecoder(true);
|
||||
private final JsonObjectDecoder fluxDecoder = new JsonObjectDecoder(true);
|
||||
|
||||
private final JsonObjectDecoder monoObjectDecoder = new JsonObjectDecoder(false);
|
||||
private final JsonObjectDecoder monoDecoder = new JsonObjectDecoder(false);
|
||||
|
||||
|
||||
public Jackson2JsonDecoder() {
|
||||
@@ -78,19 +77,17 @@ public class Jackson2JsonDecoder extends AbstractJackson2Codec implements Server
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<Object> decode(Publisher<DataBuffer> inputStream, ResolvableType elementType,
|
||||
public Flux<Object> decode(Publisher<DataBuffer> input, ResolvableType elementType,
|
||||
MimeType mimeType, Map<String, Object> hints) {
|
||||
|
||||
JsonObjectDecoder objectDecoder = this.fluxObjectDecoder;
|
||||
return decodeInternal(objectDecoder, inputStream, elementType, mimeType, hints);
|
||||
return decodeInternal(this.fluxDecoder, input, elementType, mimeType, hints);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Object> decodeToMono(Publisher<DataBuffer> inputStream, ResolvableType elementType,
|
||||
public Mono<Object> decodeToMono(Publisher<DataBuffer> input, ResolvableType elementType,
|
||||
MimeType mimeType, Map<String, Object> hints) {
|
||||
|
||||
JsonObjectDecoder objectDecoder = this.monoObjectDecoder;
|
||||
return decodeInternal(objectDecoder, inputStream, elementType, mimeType, hints).singleOrEmpty();
|
||||
return decodeInternal(this.monoDecoder, input, elementType, mimeType, hints).singleOrEmpty();
|
||||
}
|
||||
|
||||
private Flux<Object> decodeInternal(JsonObjectDecoder objectDecoder, Publisher<DataBuffer> inputStream,
|
||||
@@ -99,20 +96,13 @@ public class Jackson2JsonDecoder extends AbstractJackson2Codec implements Server
|
||||
Assert.notNull(inputStream, "'inputStream' must not be null");
|
||||
Assert.notNull(elementType, "'elementType' must not be null");
|
||||
|
||||
MethodParameter methodParam = (elementType.getSource() instanceof MethodParameter ?
|
||||
(MethodParameter) elementType.getSource() : null);
|
||||
Class<?> contextClass = (methodParam != null ? methodParam.getContainingClass() : null);
|
||||
Class<?> contextClass = getParameter(elementType).map(MethodParameter::getContainingClass).orElse(null);
|
||||
JavaType javaType = getJavaType(elementType.getType(), contextClass);
|
||||
Class<?> jsonView = (Class<?>) hints.get(Jackson2CodecSupport.JSON_VIEW_HINT);
|
||||
|
||||
ObjectReader reader;
|
||||
Class<?> jsonView = (Class<?>)hints.get(AbstractJackson2Codec.JSON_VIEW_HINT);
|
||||
|
||||
if (jsonView != null) {
|
||||
reader = this.mapper.readerWithView(jsonView).forType(javaType);
|
||||
}
|
||||
else {
|
||||
reader = this.mapper.readerFor(javaType);
|
||||
}
|
||||
ObjectReader reader = jsonView != null ?
|
||||
this.mapper.readerWithView(jsonView).forType(javaType) :
|
||||
this.mapper.readerFor(javaType);
|
||||
|
||||
return objectDecoder.decode(inputStream, elementType, mimeType, hints)
|
||||
.map(dataBuffer -> {
|
||||
@@ -134,20 +124,12 @@ public class Jackson2JsonDecoder extends AbstractJackson2Codec implements Server
|
||||
public Map<String, Object> getDecodeHints(ResolvableType actualType, ResolvableType elementType,
|
||||
ServerHttpRequest request, ServerHttpResponse response) {
|
||||
|
||||
Object source = actualType.getSource();
|
||||
MethodParameter parameter = (source instanceof MethodParameter ? (MethodParameter)source : null);
|
||||
if (parameter != null) {
|
||||
JsonView annotation = parameter.getParameterAnnotation(JsonView.class);
|
||||
if (annotation != null) {
|
||||
Class<?>[] classes = annotation.value();
|
||||
if (classes.length != 1) {
|
||||
throw new IllegalArgumentException(
|
||||
"@JsonView only supported for read hints with exactly 1 class argument: " + parameter);
|
||||
}
|
||||
return Collections.singletonMap(AbstractJackson2Codec.JSON_VIEW_HINT, classes[0]);
|
||||
}
|
||||
}
|
||||
return Collections.emptyMap();
|
||||
return getHints(actualType);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <A extends Annotation> A getAnnotation(MethodParameter parameter, Class<A> annotType) {
|
||||
return parameter.getParameterAnnotation(annotType);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,11 +18,10 @@ package org.springframework.http.codec.json;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.HashMap;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonView;
|
||||
import com.fasterxml.jackson.core.PrettyPrinter;
|
||||
import com.fasterxml.jackson.core.util.DefaultIndenter;
|
||||
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
|
||||
@@ -61,7 +60,7 @@ import static org.springframework.http.MediaType.APPLICATION_STREAM_JSON;
|
||||
* @since 5.0
|
||||
* @see Jackson2JsonDecoder
|
||||
*/
|
||||
public class Jackson2JsonEncoder extends AbstractJackson2Codec implements ServerHttpEncoder<Object> {
|
||||
public class Jackson2JsonEncoder extends Jackson2CodecSupport implements ServerHttpEncoder<Object> {
|
||||
|
||||
private final PrettyPrinter ssePrettyPrinter;
|
||||
|
||||
@@ -112,30 +111,24 @@ public class Jackson2JsonEncoder extends AbstractJackson2Codec implements Server
|
||||
}
|
||||
|
||||
private DataBuffer encodeValue(Object value, DataBufferFactory bufferFactory,
|
||||
ResolvableType type, Map<String, Object> hints) {
|
||||
ResolvableType elementType, Map<String, Object> hints) {
|
||||
|
||||
TypeFactory typeFactory = this.mapper.getTypeFactory();
|
||||
JavaType javaType = typeFactory.constructType(type.getType());
|
||||
if (type.isInstance(value)) {
|
||||
javaType = getJavaType(type.getType(), null);
|
||||
JavaType javaType = typeFactory.constructType(elementType.getType());
|
||||
if (elementType.isInstance(value)) {
|
||||
javaType = getJavaType(elementType.getType(), null);
|
||||
}
|
||||
|
||||
ObjectWriter writer;
|
||||
Class<?> jsonView = (Class<?>)hints.get(AbstractJackson2Codec.JSON_VIEW_HINT);
|
||||
if (jsonView != null) {
|
||||
writer = this.mapper.writerWithView(jsonView);
|
||||
}
|
||||
else {
|
||||
writer = this.mapper.writer();
|
||||
}
|
||||
Class<?> jsonView = (Class<?>) hints.get(Jackson2CodecSupport.JSON_VIEW_HINT);
|
||||
ObjectWriter writer = jsonView != null ? this.mapper.writerWithView(jsonView): this.mapper.writer();
|
||||
|
||||
if (javaType != null && javaType.isContainerType()) {
|
||||
writer = writer.forType(javaType);
|
||||
}
|
||||
|
||||
Boolean sse = (Boolean)hints.get(ServerSentEventHttpMessageWriter.SSE_CONTENT_HINT);
|
||||
SerializationConfig config = writer.getConfig();
|
||||
if (Boolean.TRUE.equals(sse) && config.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
|
||||
Boolean sseHint = (Boolean) hints.get(ServerSentEventHttpMessageWriter.SSE_CONTENT_HINT);
|
||||
if (Boolean.TRUE.equals(sseHint) && config.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
|
||||
writer = writer.with(this.ssePrettyPrinter);
|
||||
}
|
||||
|
||||
@@ -158,21 +151,12 @@ public class Jackson2JsonEncoder extends AbstractJackson2Codec implements Server
|
||||
public Map<String, Object> getEncodeHints(ResolvableType actualType, ResolvableType elementType,
|
||||
MediaType mediaType, ServerHttpRequest request, ServerHttpResponse response) {
|
||||
|
||||
Map<String, Object> hints = new HashMap<>();
|
||||
Object source = actualType.getSource();
|
||||
MethodParameter returnValue = (source instanceof MethodParameter ? (MethodParameter)source : null);
|
||||
if (returnValue != null) {
|
||||
JsonView annotation = returnValue.getMethodAnnotation(JsonView.class);
|
||||
if (annotation != null) {
|
||||
Class<?>[] classes = annotation.value();
|
||||
if (classes.length != 1) {
|
||||
throw new IllegalArgumentException(
|
||||
"@JsonView only supported for write hints with exactly 1 class argument: " + returnValue);
|
||||
}
|
||||
hints.put(AbstractJackson2Codec.JSON_VIEW_HINT, classes[0]);
|
||||
}
|
||||
}
|
||||
return hints;
|
||||
return getHints(actualType);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <A extends Annotation> A getAnnotation(MethodParameter parameter, Class<A> annotType) {
|
||||
return parameter.getMethodAnnotation(annotType);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user