Add JsonView and type resolution support to JacksonJsonDecoder
There is no contextClass support yet, we need to find a way to pass this information to the codecs. Issue: SPR-14158
This commit is contained in:
@@ -17,21 +17,23 @@
|
||||
package org.springframework.http.codec.json;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonView;
|
||||
import com.fasterxml.jackson.databind.JavaType;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.ObjectReader;
|
||||
import com.fasterxml.jackson.databind.type.TypeFactory;
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.codec.CodecException;
|
||||
import org.springframework.core.codec.AbstractDecoder;
|
||||
import org.springframework.core.codec.Decoder;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.support.DataBufferUtils;
|
||||
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.MimeType;
|
||||
|
||||
@@ -44,15 +46,7 @@ import org.springframework.util.MimeType;
|
||||
* @since 5.0
|
||||
* @see JacksonJsonEncoder
|
||||
*/
|
||||
public class JacksonJsonDecoder extends AbstractDecoder<Object> {
|
||||
|
||||
private static final MimeType[] MIME_TYPES = new MimeType[] {
|
||||
new MimeType("application", "json", StandardCharsets.UTF_8),
|
||||
new MimeType("application", "*+json", StandardCharsets.UTF_8)
|
||||
};
|
||||
|
||||
|
||||
private final ObjectMapper mapper;
|
||||
public class JacksonJsonDecoder extends AbstractJacksonJsonCodec implements Decoder<Object> {
|
||||
|
||||
private final JsonObjectDecoder fluxObjectDecoder = new JsonObjectDecoder(true);
|
||||
|
||||
@@ -60,14 +54,25 @@ public class JacksonJsonDecoder extends AbstractDecoder<Object> {
|
||||
|
||||
|
||||
public JacksonJsonDecoder() {
|
||||
this(new ObjectMapper());
|
||||
super(Jackson2ObjectMapperBuilder.json().build());
|
||||
}
|
||||
|
||||
public JacksonJsonDecoder(ObjectMapper mapper) {
|
||||
super(MIME_TYPES);
|
||||
this.mapper = mapper;
|
||||
super(mapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canDecode(ResolvableType elementType, MimeType mimeType, Object... hints) {
|
||||
if (mimeType == null) {
|
||||
return true;
|
||||
}
|
||||
return JSON_MIME_TYPES.stream().anyMatch(m -> m.isCompatibleWith(mimeType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MimeType> getDecodableMimeTypes() {
|
||||
return JSON_MIME_TYPES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<Object> decode(Publisher<DataBuffer> inputStream, ResolvableType elementType,
|
||||
@@ -91,10 +96,24 @@ public class JacksonJsonDecoder extends AbstractDecoder<Object> {
|
||||
Assert.notNull(inputStream, "'inputStream' must not be null");
|
||||
Assert.notNull(elementType, "'elementType' must not be null");
|
||||
|
||||
TypeFactory typeFactory = this.mapper.getTypeFactory();
|
||||
JavaType javaType = typeFactory.constructType(elementType.getType());
|
||||
MethodParameter methodParameter = (elementType.getSource() instanceof MethodParameter ?
|
||||
(MethodParameter)elementType.getSource() : null);
|
||||
// TODO Find a way to pass the real concrete controller contextClass
|
||||
JavaType javaType = getJavaType(elementType.getType(), null);
|
||||
ObjectReader reader;
|
||||
|
||||
ObjectReader reader = this.mapper.readerFor(javaType);
|
||||
if (methodParameter != null && methodParameter.getParameter().getAnnotation(JsonView.class) != null) {
|
||||
JsonView annotation = methodParameter.getParameter().getAnnotation(JsonView.class);
|
||||
Class<?>[] classes = annotation.value();
|
||||
if (classes.length != 1) {
|
||||
throw new IllegalArgumentException(
|
||||
"@JsonView only supported for response body advice with exactly 1 class argument: " + methodParameter);
|
||||
}
|
||||
reader = mapper.readerWithView(classes[0]).forType(javaType);
|
||||
}
|
||||
else {
|
||||
reader = mapper.readerFor(javaType);
|
||||
}
|
||||
|
||||
return objectDecoder.decode(inputStream, elementType, mimeType, hints)
|
||||
.map(dataBuffer -> {
|
||||
|
||||
@@ -20,6 +20,8 @@ import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonView;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
@@ -84,7 +86,74 @@ public class JacksonJsonDecoderTests extends AbstractDataBufferAllocatingTestCas
|
||||
assertValues(new Pojo("f1", "b1"), new Pojo("f2", "b2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void jsonView() throws Exception {
|
||||
Flux<DataBuffer> source = Flux.just(
|
||||
stringBuffer("{\"withView1\" : \"with\", \"withView2\" : \"with\", \"withoutView\" : \"without\"}"));
|
||||
ResolvableType elementType = ResolvableType
|
||||
.forMethodParameter(JacksonController.class.getMethod("foo", JacksonViewBean.class), 0);
|
||||
Flux<JacksonViewBean> flux = new JacksonJsonDecoder()
|
||||
.decode(source, elementType, null).cast(JacksonViewBean.class);
|
||||
|
||||
TestSubscriber
|
||||
.subscribe(flux)
|
||||
.assertNoError()
|
||||
.assertComplete()
|
||||
.assertValuesWith(b -> {
|
||||
assertTrue(b.getWithView1().equals("with"));
|
||||
assertNull(b.getWithView2());
|
||||
assertNull(b.getWithoutView());
|
||||
});
|
||||
}
|
||||
|
||||
void handle(List<Pojo> list) {
|
||||
}
|
||||
|
||||
private interface MyJacksonView1 {}
|
||||
|
||||
private interface MyJacksonView2 {}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static class JacksonViewBean {
|
||||
|
||||
@JsonView(MyJacksonView1.class)
|
||||
private String withView1;
|
||||
|
||||
@JsonView(MyJacksonView2.class)
|
||||
private String withView2;
|
||||
|
||||
private String withoutView;
|
||||
|
||||
public String getWithView1() {
|
||||
return withView1;
|
||||
}
|
||||
|
||||
public void setWithView1(String withView1) {
|
||||
this.withView1 = withView1;
|
||||
}
|
||||
|
||||
public String getWithView2() {
|
||||
return withView2;
|
||||
}
|
||||
|
||||
public void setWithView2(String withView2) {
|
||||
this.withView2 = withView2;
|
||||
}
|
||||
|
||||
public String getWithoutView() {
|
||||
return withoutView;
|
||||
}
|
||||
|
||||
public void setWithoutView(String withoutView) {
|
||||
this.withoutView = withoutView;
|
||||
}
|
||||
}
|
||||
|
||||
private static class JacksonController {
|
||||
|
||||
public JacksonViewBean foo(@JsonView(MyJacksonView1.class) JacksonViewBean bean) {
|
||||
return bean;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user