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 0cf1d3f0218..98d23ad2ac1 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 @@ -76,10 +76,11 @@ public abstract class AbstractJackson2Encoder extends Jackson2CodecSupport imple STREAM_SEPARATORS.put(MediaType.APPLICATION_STREAM_JSON, NEWLINE_SEPARATOR); STREAM_SEPARATORS.put(MediaType.parseMediaType("application/stream+x-jackson-smile"), new byte[0]); - ENCODINGS = new HashMap<>(JsonEncoding.values().length); + ENCODINGS = new HashMap<>(JsonEncoding.values().length + 1); for (JsonEncoding encoding : JsonEncoding.values()) { ENCODINGS.put(encoding.getJavaName(), encoding); } + ENCODINGS.put("US-ASCII", JsonEncoding.UTF8); } diff --git a/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java index c9df1273673..eda3fd768f1 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java @@ -24,11 +24,9 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collections; -import java.util.EnumSet; +import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Function; -import java.util.stream.Collectors; import com.fasterxml.jackson.core.JsonEncoding; import com.fasterxml.jackson.core.JsonGenerator; @@ -76,7 +74,16 @@ import org.springframework.util.TypeUtils; */ public abstract class AbstractJackson2HttpMessageConverter extends AbstractGenericHttpMessageConverter { - private static final Map ENCODINGS = jsonEncodings(); + private static final Map ENCODINGS; + + static { + ENCODINGS = new HashMap<>(JsonEncoding.values().length + 1); + for (JsonEncoding encoding : JsonEncoding.values()) { + ENCODINGS.put(encoding.getJavaName(), encoding); + } + ENCODINGS.put("US-ASCII", JsonEncoding.UTF8); + } + /** * The default charset used by the converter. @@ -398,9 +405,4 @@ public abstract class AbstractJackson2HttpMessageConverter extends AbstractGener return super.getContentLength(object, contentType); } - private static Map jsonEncodings() { - return EnumSet.allOf(JsonEncoding.class).stream() - .collect(Collectors.toMap(JsonEncoding::getJavaName, Function.identity())); - } - } diff --git a/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2JsonDecoderTests.java b/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2JsonDecoderTests.java index 2e54321bdd6..eea21cce103 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2JsonDecoderTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2JsonDecoderTests.java @@ -91,6 +91,8 @@ public class Jackson2JsonDecoderTests extends AbstractDecoderTestCase>() { - }), + testDecode(input, ResolvableType.forType(new ParameterizedTypeReference>() {}), step -> step.assertNext(o -> { assertTrue(o instanceof Map); Map map = (Map) o; @@ -263,8 +264,7 @@ public class Jackson2JsonDecoderTests extends AbstractDecoderTestCase input = stringBuffer("{\"foo\":\"bar\"}", StandardCharsets.UTF_16); - testDecodeToMono(input, ResolvableType.forType(new ParameterizedTypeReference>() { - }), + testDecodeToMono(input, ResolvableType.forType(new ParameterizedTypeReference>() {}), step -> step.assertNext(o -> { Map map = (Map) o; assertEquals("bar", map.get("foo")); @@ -274,6 +274,24 @@ public class Jackson2JsonDecoderTests extends AbstractDecoderTestCase input = Flux.concat( + stringBuffer("{\"foo\":\"bar\"}", StandardCharsets.US_ASCII) + ); + + testDecode(input, ResolvableType.forType(new ParameterizedTypeReference>() {}), + step -> step.assertNext(o -> { + Map map = (Map) o; + assertEquals("bar", map.get("foo")); + }) + .verifyComplete(), + MediaType.parseMediaType("application/json; charset=us-ascii"), + null); + } + + private Mono stringBuffer(String value) { return stringBuffer(value, StandardCharsets.UTF_8); } diff --git a/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2JsonEncoderTests.java b/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2JsonEncoderTests.java index a6543b86b9d..ca237cb26e6 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2JsonEncoderTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2JsonEncoderTests.java @@ -42,7 +42,9 @@ import org.springframework.util.MimeType; import org.springframework.util.MimeTypeUtils; import static java.util.Collections.singletonMap; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8; import static org.springframework.http.MediaType.APPLICATION_OCTET_STREAM; @@ -73,6 +75,8 @@ public class Jackson2JsonEncoderTests extends AbstractEncoderTestCase input = Mono.just(new Pojo("foo", "bar")); + + testEncode(input, ResolvableType.forClass(Pojo.class), step -> step + .consumeNextWith(expectString("{\"foo\":\"foo\",\"bar\":\"bar\"}")) + .verifyComplete(), + new MimeType("application", "json", StandardCharsets.US_ASCII), null); + + } + @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") private static class ParentClass { diff --git a/spring-web/src/test/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverterTests.java index 0bb83206ee8..a2f9308a3fc 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverterTests.java @@ -43,8 +43,14 @@ import org.springframework.http.converter.HttpMessageConversionException; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.lang.Nullable; -import static org.hamcrest.CoreMatchers.*; -import static org.junit.Assert.*; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.not; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; /** * Jackson 2.x converter tests. @@ -65,6 +71,7 @@ public class MappingJackson2HttpMessageConverterTests { assertTrue(converter.canRead(MyBean.class, new MediaType("application", "json"))); assertTrue(converter.canRead(Map.class, new MediaType("application", "json"))); assertTrue(converter.canRead(MyBean.class, new MediaType("application", "json", StandardCharsets.UTF_8))); + assertTrue(converter.canRead(MyBean.class, new MediaType("application", "json", StandardCharsets.US_ASCII))); assertTrue(converter.canRead(MyBean.class, new MediaType("application", "json", StandardCharsets.ISO_8859_1))); } @@ -73,6 +80,7 @@ public class MappingJackson2HttpMessageConverterTests { assertTrue(converter.canWrite(MyBean.class, new MediaType("application", "json"))); assertTrue(converter.canWrite(Map.class, new MediaType("application", "json"))); assertTrue(converter.canWrite(MyBean.class, new MediaType("application", "json", StandardCharsets.UTF_8))); + assertTrue(converter.canWrite(MyBean.class, new MediaType("application", "json", StandardCharsets.US_ASCII))); assertFalse(converter.canWrite(MyBean.class, new MediaType("application", "json", StandardCharsets.ISO_8859_1))); } @@ -465,6 +473,34 @@ public class MappingJackson2HttpMessageConverterTests { assertEquals("bår", result.get("føø")); } + @Test + @SuppressWarnings("unchecked") + public void readAscii() throws Exception { + String body = "{\"foo\":\"bar\"}"; + Charset charset = StandardCharsets.US_ASCII; + MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(charset)); + inputMessage.getHeaders().setContentType(new MediaType("application", "json", charset)); + HashMap result = (HashMap) this.converter.read(HashMap.class, inputMessage); + + assertEquals(1, result.size()); + assertEquals("bar", result.get("foo")); + } + + @Test + @SuppressWarnings("unchecked") + public void writeAscii() throws Exception { + MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); + Map body = new HashMap<>(); + body.put("foo", "bar"); + Charset charset = StandardCharsets.US_ASCII; + MediaType contentType = new MediaType("application", "json", charset); + converter.write(body, contentType, outputMessage); + + String result = outputMessage.getBodyAsString(charset); + assertEquals("{\"foo\":\"bar\"}", result); + assertEquals(contentType, outputMessage.getHeaders().getContentType()); + } + interface MyInterface {