Browse Source

Add Jackson @JsonView support

Issue: SPR-14693
pull/1173/merge
Sebastien Deleuze 9 years ago
parent
commit
6cda08e94b
  1. 8
      spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfiguration.java
  2. 190
      spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/JacksonHintsIntegrationTests.java
  3. 66
      spring-web/src/main/java/org/springframework/http/codec/Jackson2ServerHttpMessageReader.java
  4. 66
      spring-web/src/main/java/org/springframework/http/codec/Jackson2ServerHttpMessageWriter.java

8
spring-web-reactive/src/main/java/org/springframework/web/reactive/config/WebReactiveConfiguration.java

@ -60,6 +60,8 @@ import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuild @@ -60,6 +60,8 @@ import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuild
import org.springframework.web.reactive.handler.AbstractHandlerMapping;
import org.springframework.web.reactive.result.SimpleHandlerAdapter;
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
import org.springframework.http.codec.Jackson2ServerHttpMessageReader;
import org.springframework.http.codec.Jackson2ServerHttpMessageWriter;
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.reactive.result.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.reactive.result.method.annotation.ResponseBodyResultHandler;
@ -286,7 +288,7 @@ public class WebReactiveConfiguration implements ApplicationContextAware { @@ -286,7 +288,7 @@ public class WebReactiveConfiguration implements ApplicationContextAware {
readers.add(new DecoderHttpMessageReader<>(new Jaxb2XmlDecoder()));
}
if (jackson2Present) {
readers.add(new DecoderHttpMessageReader<>(new Jackson2JsonDecoder()));
readers.add(new Jackson2ServerHttpMessageReader(new DecoderHttpMessageReader<>(new Jackson2JsonDecoder())));
}
}
@ -404,10 +406,10 @@ public class WebReactiveConfiguration implements ApplicationContextAware { @@ -404,10 +406,10 @@ public class WebReactiveConfiguration implements ApplicationContextAware {
}
if (jackson2Present) {
Jackson2JsonEncoder jacksonEncoder = new Jackson2JsonEncoder();
writers.add(new EncoderHttpMessageWriter<>(jacksonEncoder));
writers.add(new Jackson2ServerHttpMessageWriter(new EncoderHttpMessageWriter<>(jacksonEncoder)));
sseDataEncoders.add(jacksonEncoder);
}
writers.add(new ServerSentEventHttpMessageWriter(sseDataEncoders));
writers.add(new Jackson2ServerHttpMessageWriter(new ServerSentEventHttpMessageWriter(sseDataEncoders)));
}
/**
* Override this to modify the list of message writers after it has been

190
spring-web-reactive/src/test/java/org/springframework/web/reactive/result/method/annotation/JacksonHintsIntegrationTests.java

@ -0,0 +1,190 @@ @@ -0,0 +1,190 @@
/*
* Copyright 2002-2016 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
*
* http://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.web.reactive.result.method.annotation;
import java.util.Arrays;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonView;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.config.WebReactiveConfiguration;
/**
* @author Sebastien Deleuze
*/
public class JacksonHintsIntegrationTests extends AbstractRequestMappingIntegrationTests {
@Override
protected ApplicationContext initApplicationContext() {
AnnotationConfigApplicationContext wac = new AnnotationConfigApplicationContext();
wac.register(WebConfig.class);
wac.refresh();
return wac;
}
@Test
public void jsonViewResponse() throws Exception {
String expected = "{\"withView1\":\"with\"}";
assertEquals(expected, performGet("/response/raw", MediaType.APPLICATION_JSON_UTF8, String.class).getBody());
}
@Test
public void jsonViewWithMonoResponse() throws Exception {
String expected = "{\"withView1\":\"with\"}";
assertEquals(expected, performGet("/response/mono", MediaType.APPLICATION_JSON_UTF8, String.class).getBody());
}
@Test
public void jsonViewWithFluxResponse() throws Exception {
String expected = "[{\"withView1\":\"with\"},{\"withView1\":\"with\"}]";
assertEquals(expected, performGet("/response/flux", MediaType.APPLICATION_JSON_UTF8, String.class).getBody());
}
@Test
public void jsonViewWithRequest() throws Exception {
String expected = "{\"withView1\":\"with\",\"withView2\":null,\"withoutView\":null}";
assertEquals(expected, performPost("/request/raw", MediaType.APPLICATION_JSON,
new JacksonViewBean("with", "with", "without"), MediaType.APPLICATION_JSON_UTF8, String.class).getBody());
}
@Test
public void jsonViewWithMonoRequest() throws Exception {
String expected = "{\"withView1\":\"with\",\"withView2\":null,\"withoutView\":null}";
assertEquals(expected, performPost("/request/mono", MediaType.APPLICATION_JSON,
new JacksonViewBean("with", "with", "without"), MediaType.APPLICATION_JSON_UTF8, String.class).getBody());
}
@Test
public void jsonViewWithFluxRequest() throws Exception {
String expected = "[{\"withView1\":\"with\",\"withView2\":null,\"withoutView\":null}," +
"{\"withView1\":\"with\",\"withView2\":null,\"withoutView\":null}]";
List<JacksonViewBean> beans = Arrays.asList(new JacksonViewBean("with", "with", "without"), new JacksonViewBean("with", "with", "without"));
assertEquals(expected, performPost("/request/flux", MediaType.APPLICATION_JSON, beans,
MediaType.APPLICATION_JSON_UTF8, String.class).getBody());
}
@Configuration
@ComponentScan(resourcePattern = "**/JacksonHintsIntegrationTests*.class")
@SuppressWarnings({"unused", "WeakerAccess"})
static class WebConfig extends WebReactiveConfiguration {
}
@RestController
@SuppressWarnings("unused")
private static class JsonViewRestController {
@GetMapping("/response/raw")
@JsonView(MyJacksonView1.class)
public JacksonViewBean rawResponse() {
return new JacksonViewBean("with", "with", "without");
}
@GetMapping("/response/mono")
@JsonView(MyJacksonView1.class)
public Mono<JacksonViewBean> monoResponse() {
return Mono.just(new JacksonViewBean("with", "with", "without"));
}
@GetMapping("/response/flux")
@JsonView(MyJacksonView1.class)
public Flux<JacksonViewBean> fluxResponse() {
return Flux.just(new JacksonViewBean("with", "with", "without"), new JacksonViewBean("with", "with", "without"));
}
@PostMapping("/request/raw")
public JacksonViewBean rawRequest(@JsonView(MyJacksonView1.class) @RequestBody JacksonViewBean bean) {
return bean;
}
@PostMapping("/request/mono")
public Mono<JacksonViewBean> monoRequest(@JsonView(MyJacksonView1.class) @RequestBody Mono<JacksonViewBean> mono) {
return mono;
}
@PostMapping("/request/flux")
public Flux<JacksonViewBean> fluxRequest(@JsonView(MyJacksonView1.class) @RequestBody Flux<JacksonViewBean> flux) {
return flux;
}
}
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 JacksonViewBean() {
}
public JacksonViewBean(String withView1, String withView2, String withoutView) {
this.withView1 = withView1;
this.withView2 = withView2;
this.withoutView = 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;
}
}
}

66
spring-web/src/main/java/org/springframework/http/codec/Jackson2ServerHttpMessageReader.java

@ -0,0 +1,66 @@ @@ -0,0 +1,66 @@
/*
* Copyright 2002-2016 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
*
* http://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.http.codec;
import java.util.Collections;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonView;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.http.MediaType;
import org.springframework.http.codec.json.AbstractJackson2Codec;
import org.springframework.http.server.reactive.ServerHttpRequest;
/**
* {@link ServerHttpMessageReader} that resolves those annotation or request based Jackson 2 hints:
* <ul>
* <li>{@code @JsonView} + {@code @RequestBody} annotated handler method parameter</li>
* </ul>
*
* @author Sebastien Deleuze
* @since 5.0
* @see com.fasterxml.jackson.annotation.JsonView
*/
public class Jackson2ServerHttpMessageReader extends AbstractServerHttpMessageReader<Object> {
public Jackson2ServerHttpMessageReader(HttpMessageReader<Object> reader) {
super(reader);
}
@Override
protected Map<String, Object> resolveReadHintsInternal(ResolvableType streamType,
ResolvableType elementType, MediaType mediaType, ServerHttpRequest request) {
Object source = streamType.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();
}
}

66
spring-web/src/main/java/org/springframework/http/codec/Jackson2ServerHttpMessageWriter.java

@ -0,0 +1,66 @@ @@ -0,0 +1,66 @@
/*
* Copyright 2002-2016 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
*
* http://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.http.codec;
import java.util.Collections;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonView;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.http.MediaType;
import org.springframework.http.codec.json.AbstractJackson2Codec;
import org.springframework.http.server.reactive.ServerHttpRequest;
/**
* {@link ServerHttpMessageWriter} that resolves those annotation or request based Jackson 2 hints:
* <ul>
* <li>{@code @JsonView} annotated handler method</li>
* </ul>
*
* @author Sebastien Deleuze
* @since 5.0
* @see com.fasterxml.jackson.annotation.JsonView
*/
public class Jackson2ServerHttpMessageWriter extends AbstractServerHttpMessageWriter<Object> {
public Jackson2ServerHttpMessageWriter(HttpMessageWriter<Object> writer) {
super(writer);
}
@Override
protected Map<String, Object> resolveWriteHintsInternal(ResolvableType streamType,
ResolvableType elementType, MediaType mediaType, ServerHttpRequest request) {
Object source = streamType.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);
}
return Collections.singletonMap(AbstractJackson2Codec.JSON_VIEW_HINT, classes[0]);
}
}
return Collections.emptyMap();
}
}
Loading…
Cancel
Save