diff --git a/spring-web/src/main/java/org/springframework/http/codec/AbstractJacksonEncoder.java b/spring-web/src/main/java/org/springframework/http/codec/AbstractJacksonEncoder.java index 75a2e47f7f9..ca7aa90e7f8 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/AbstractJacksonEncoder.java +++ b/spring-web/src/main/java/org/springframework/http/codec/AbstractJacksonEncoder.java @@ -161,7 +161,7 @@ public abstract class AbstractJacksonEncoder extends Jac throw new IllegalStateException("No ObjectMapper for " + elementType); } - ObjectWriter writer = createObjectWriter(mapper, elementType, mimeType, null, hintsToUse); + ObjectWriter writer = createObjectWriter(mapper, elementType, mimeType, hintsToUse); ByteArrayBuilder byteBuilder = new ByteArrayBuilder(writer.generatorFactory()._getBufferRecycler()); JsonEncoding encoding = getJsonEncoding(mimeType); JsonGenerator generator = mapper.createGenerator(byteBuilder, encoding); @@ -219,22 +219,12 @@ public abstract class AbstractJacksonEncoder extends Jac public DataBuffer encodeValue(Object value, DataBufferFactory bufferFactory, ResolvableType valueType, @Nullable MimeType mimeType, @Nullable Map hints) { - Class jsonView = null; - FilterProvider filters = null; - if (hints != null) { - jsonView = (Class) hints.get(JSON_VIEW_HINT); - filters = (FilterProvider) hints.get(FILTER_PROVIDER_HINT); - } - T mapper = selectMapper(valueType, mimeType); if (mapper == null) { throw new IllegalStateException("No ObjectMapper for " + valueType); } - ObjectWriter writer = createObjectWriter(mapper, valueType, mimeType, jsonView, hints); - if (filters != null) { - writer = writer.with(filters); - } + ObjectWriter writer = createObjectWriter(mapper, valueType, mimeType, hints); ByteArrayBuilder byteBuilder = new ByteArrayBuilder(writer.generatorFactory()._getBufferRecycler()); try { @@ -321,13 +311,19 @@ public abstract class AbstractJacksonEncoder extends Jac private ObjectWriter createObjectWriter( T mapper, ResolvableType valueType, @Nullable MimeType mimeType, - @Nullable Class jsonView, @Nullable Map hints) { + @Nullable Map hints) { JavaType javaType = getJavaType(valueType.getType(), null); - if (jsonView == null && hints != null) { + Class jsonView = null; + FilterProvider filters = null; + if (hints != null) { jsonView = (Class) hints.get(JacksonCodecSupport.JSON_VIEW_HINT); + filters = (FilterProvider) hints.get(FILTER_PROVIDER_HINT); } ObjectWriter writer = (jsonView != null ? mapper.writerWithView(jsonView) : mapper.writer()); + if (filters != null) { + writer = writer.with(filters); + } if (javaType.isContainerType()) { writer = writer.forType(javaType); } diff --git a/spring-web/src/test/java/org/springframework/http/codec/json/JacksonJsonEncoderTests.java b/spring-web/src/test/java/org/springframework/http/codec/json/JacksonJsonEncoderTests.java index 3c2208c0aa1..46b92439dfc 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/json/JacksonJsonEncoderTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/json/JacksonJsonEncoderTests.java @@ -22,6 +22,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.Map; +import com.fasterxml.jackson.annotation.JsonFilter; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; import org.junit.jupiter.api.Test; @@ -30,6 +31,9 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; import tools.jackson.databind.SerializationFeature; import tools.jackson.databind.json.JsonMapper; +import tools.jackson.databind.ser.FilterProvider; +import tools.jackson.databind.ser.std.SimpleBeanPropertyFilter; +import tools.jackson.databind.ser.std.SimpleFilterProvider; import org.springframework.core.ResolvableType; import org.springframework.core.io.buffer.DataBuffer; @@ -50,6 +54,7 @@ import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.http.MediaType.APPLICATION_NDJSON; import static org.springframework.http.MediaType.APPLICATION_OCTET_STREAM; import static org.springframework.http.MediaType.APPLICATION_XML; +import static org.springframework.http.codec.JacksonCodecSupport.FILTER_PROVIDER_HINT; import static org.springframework.http.codec.JacksonCodecSupport.JSON_VIEW_HINT; /** @@ -211,6 +216,24 @@ class JacksonJsonEncoderTests extends AbstractEncoderTests { ); } + @Test + void fieldLevelJsonViewStream() { + JacksonViewBean bean = new JacksonViewBean(); + bean.setWithView1("with"); + bean.setWithView2("with"); + bean.setWithoutView("without"); + Flux input = Flux.just(bean, bean); + + ResolvableType type = ResolvableType.forClass(JacksonViewBean.class); + Map hints = singletonMap(JSON_VIEW_HINT, MyJacksonView1.class); + + testEncodeAll(input, type, APPLICATION_NDJSON, hints, step -> step + .consumeNextWith(expectString("{\"withView1\":\"with\"}\n")) + .consumeNextWith(expectString("{\"withView1\":\"with\"}\n")) + .verifyComplete() + ); + } + @Test void classLevelJsonView() { JacksonViewBean bean = new JacksonViewBean(); @@ -228,6 +251,33 @@ class JacksonJsonEncoderTests extends AbstractEncoderTests { ); } + @Test + void filterProvider() { + JacksonFilteredBean filteredBean = new JacksonFilteredBean("foo", "bar"); + FilterProvider filters = new SimpleFilterProvider().addFilter("myJacksonFilter", + SimpleBeanPropertyFilter.serializeAllExcept("property2")); + Mono input = Mono.just(filteredBean); + Map hints = singletonMap(FILTER_PROVIDER_HINT, filters); + testEncode(input, ResolvableType.forClass(Pojo.class), APPLICATION_JSON, hints, step -> step + .consumeNextWith(expectString("{\"property1\":\"foo\"}")) + .verifyComplete() + ); + } + + @Test + void filterProviderStream() { + JacksonFilteredBean filteredBean = new JacksonFilteredBean("foo", "bar"); + FilterProvider filters = new SimpleFilterProvider().addFilter("myJacksonFilter", + SimpleBeanPropertyFilter.serializeAllExcept("property2")); + Flux input = Flux.just(filteredBean, filteredBean); + Map hints = singletonMap(FILTER_PROVIDER_HINT, filters); + testEncodeAll(input, ResolvableType.forClass(Pojo.class), APPLICATION_NDJSON, hints, step -> step + .consumeNextWith(expectString("{\"property1\":\"foo\"}\n")) + .consumeNextWith(expectString("{\"property1\":\"foo\"}\n")) + .verifyComplete() + ); + } + @Test // gh-22771 public void encodeWithFlushAfterWriteOff() { JsonMapper mapper = JsonMapper.builder().configure(SerializationFeature.FLUSH_AFTER_WRITE_VALUE, false).build(); @@ -267,4 +317,8 @@ class JacksonJsonEncoderTests extends AbstractEncoderTests { private static class Bar extends ParentClass { } + @JsonFilter("myJacksonFilter") + record JacksonFilteredBean(String property1, String property2) { + } + }