diff --git a/spring-web/src/main/java/org/springframework/http/codec/json/JacksonJsonDecoder.java b/spring-web/src/main/java/org/springframework/http/codec/json/JacksonJsonDecoder.java index 89b08e4d37d..5b0dca59858 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/json/JacksonJsonDecoder.java +++ b/spring-web/src/main/java/org/springframework/http/codec/json/JacksonJsonDecoder.java @@ -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 { - - 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 { private final JsonObjectDecoder fluxObjectDecoder = new JsonObjectDecoder(true); @@ -60,14 +54,25 @@ public class JacksonJsonDecoder extends AbstractDecoder { 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 getDecodableMimeTypes() { + return JSON_MIME_TYPES; + } @Override public Flux decode(Publisher inputStream, ResolvableType elementType, @@ -91,10 +96,24 @@ public class JacksonJsonDecoder extends AbstractDecoder { 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()); - - ObjectReader reader = this.mapper.readerFor(javaType); + 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; + + 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 -> { diff --git a/spring-web/src/test/java/org/springframework/http/codec/json/JacksonJsonDecoderTests.java b/spring-web/src/test/java/org/springframework/http/codec/json/JacksonJsonDecoderTests.java index 822b506a6be..6159a0f6695 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/json/JacksonJsonDecoderTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/json/JacksonJsonDecoderTests.java @@ -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 source = Flux.just( + stringBuffer("{\"withView1\" : \"with\", \"withView2\" : \"with\", \"withoutView\" : \"without\"}")); + ResolvableType elementType = ResolvableType + .forMethodParameter(JacksonController.class.getMethod("foo", JacksonViewBean.class), 0); + Flux 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 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; + } + } + }