diff --git a/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Encoder.java b/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Encoder.java index 6fef5ef4855..89062082a50 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Encoder.java +++ b/spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Encoder.java @@ -19,12 +19,15 @@ package org.springframework.http.codec.json; import java.io.IOException; import java.io.OutputStream; import java.lang.annotation.Annotation; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import com.fasterxml.jackson.core.JsonEncoding; +import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; @@ -109,16 +112,18 @@ public abstract class AbstractJackson2Encoder extends Jackson2CodecSupport imple Assert.notNull(bufferFactory, "'bufferFactory' must not be null"); Assert.notNull(elementType, "'elementType' must not be null"); + JsonEncoding encoding = getJsonEncoding(mimeType); + if (inputStream instanceof Mono) { return Flux.from(inputStream).map(value -> - encodeValue(value, mimeType, bufferFactory, elementType, hints)); + encodeValue(value, mimeType, bufferFactory, elementType, hints, encoding)); } for (MediaType streamingMediaType : this.streamingMediaTypes) { if (streamingMediaType.isCompatibleWith(mimeType)) { byte[] separator = STREAM_SEPARATORS.getOrDefault(streamingMediaType, NEWLINE_SEPARATOR); return Flux.from(inputStream).map(value -> { - DataBuffer buffer = encodeValue(value, mimeType, bufferFactory, elementType, hints); + DataBuffer buffer = encodeValue(value, mimeType, bufferFactory, elementType, hints, encoding); if (separator != null) { buffer.write(separator); } @@ -129,11 +134,11 @@ public abstract class AbstractJackson2Encoder extends Jackson2CodecSupport imple ResolvableType listType = ResolvableType.forClassWithGenerics(List.class, elementType); return Flux.from(inputStream).collectList().map(list -> - encodeValue(list, mimeType, bufferFactory, listType, hints)).flux(); + encodeValue(list, mimeType, bufferFactory, listType, hints, encoding)).flux(); } private DataBuffer encodeValue(Object value, @Nullable MimeType mimeType, DataBufferFactory bufferFactory, - ResolvableType elementType, @Nullable Map hints) { + ResolvableType elementType, @Nullable Map hints, JsonEncoding encoding) { JavaType javaType = getJavaType(elementType.getType(), null); Class jsonView = (hints != null ? (Class) hints.get(Jackson2CodecSupport.JSON_VIEW_HINT) : null); @@ -148,8 +153,10 @@ public abstract class AbstractJackson2Encoder extends Jackson2CodecSupport imple DataBuffer buffer = bufferFactory.allocateBuffer(); OutputStream outputStream = buffer.asOutputStream(); + try { - writer.writeValue(outputStream, value); + JsonGenerator generator = getObjectMapper().getFactory().createGenerator(outputStream, encoding); + writer.writeValue(generator, value); } catch (InvalidDefinitionException ex) { throw new CodecException("Type definition error: " + ex.getType(), ex); @@ -170,6 +177,24 @@ public abstract class AbstractJackson2Encoder extends Jackson2CodecSupport imple return writer; } + /** + * Determine the JSON encoding to use for the given mime type. + * @param mimeType the mime type as requested by the caller + * @return the JSON encoding to use (never {@code null}) + * @since 5.0.5 + */ + protected JsonEncoding getJsonEncoding(@Nullable MimeType mimeType) { + if (mimeType != null && mimeType.getCharset() != null) { + Charset charset = mimeType.getCharset(); + for (JsonEncoding encoding : JsonEncoding.values()) { + if (charset.name().equals(encoding.getJavaName())) { + return encoding; + } + } + } + return JsonEncoding.UTF8; + } + // HttpMessageEncoder... diff --git a/spring-web/src/test/java/org/springframework/http/codec/ServerSentEventHttpMessageWriterTests.java b/spring-web/src/test/java/org/springframework/http/codec/ServerSentEventHttpMessageWriterTests.java index a3e237c10ee..f7f1195ccdb 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/ServerSentEventHttpMessageWriterTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/ServerSentEventHttpMessageWriterTests.java @@ -23,7 +23,6 @@ import java.util.Collections; import java.util.Map; import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.Ignore; import org.junit.Test; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; @@ -154,20 +153,18 @@ public class ServerSentEventHttpMessageWriterTests extends AbstractDataBufferAll .verify(); } - @Ignore @Test // SPR-16516, SPR-16539 public void writePojoWithCustomEncoding() { - Flux source = Flux.just(new Pojo("foo\u00A3", "bar\u00A3")); - Charset charset = StandardCharsets.ISO_8859_1; + Flux source = Flux.just(new Pojo("foo\uD834\uDD1E", "bar\uD834\uDD1E")); + Charset charset = StandardCharsets.UTF_16LE; MediaType mediaType = new MediaType("text", "event-stream", charset); MockServerHttpResponse outputMessage = new MockServerHttpResponse(); testWrite(source, mediaType, outputMessage, Pojo.class); assertEquals(mediaType, outputMessage.getHeaders().getContentType()); StepVerifier.create(outputMessage.getBodyAsString()) - .expectNext("data:{\"foo\":\"foo\u00A3\",\"bar\":\"bar\u00A3\"}\n\n") - .expectComplete() - .verify(); + .expectNext("data:{\"foo\":\"foo\uD834\uDD1E\",\"bar\":\"bar\uD834\uDD1E\"}\n\n") + .verifyComplete(); }