Skip to content

Commit f4ae18f

Browse files
committed
Support for ASCII in Jackson codec & converter
This commit introduces support for writing JSON with an US-ASCII character encoding in the Jackson encoder and message converter, treating it like UTF-8. See gh-25322 (cherry picked from commit 79c339b)
1 parent fc5a6db commit f4ae18f

File tree

5 files changed

+89
-17
lines changed

5 files changed

+89
-17
lines changed

spring-web/src/main/java/org/springframework/http/codec/json/AbstractJackson2Encoder.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,11 @@ public abstract class AbstractJackson2Encoder extends Jackson2CodecSupport imple
7676
STREAM_SEPARATORS.put(MediaType.APPLICATION_STREAM_JSON, NEWLINE_SEPARATOR);
7777
STREAM_SEPARATORS.put(MediaType.parseMediaType("application/stream+x-jackson-smile"), new byte[0]);
7878

79-
ENCODINGS = new HashMap<>(JsonEncoding.values().length);
79+
ENCODINGS = new HashMap<>(JsonEncoding.values().length + 1);
8080
for (JsonEncoding encoding : JsonEncoding.values()) {
8181
ENCODINGS.put(encoding.getJavaName(), encoding);
8282
}
83+
ENCODINGS.put("US-ASCII", JsonEncoding.UTF8);
8384
}
8485

8586

spring-web/src/main/java/org/springframework/http/converter/json/AbstractJackson2HttpMessageConverter.java

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,9 @@
2424
import java.nio.charset.StandardCharsets;
2525
import java.util.Arrays;
2626
import java.util.Collections;
27-
import java.util.EnumSet;
27+
import java.util.HashMap;
2828
import java.util.Map;
2929
import java.util.concurrent.atomic.AtomicReference;
30-
import java.util.function.Function;
31-
import java.util.stream.Collectors;
3230

3331
import com.fasterxml.jackson.core.JsonEncoding;
3432
import com.fasterxml.jackson.core.JsonGenerator;
@@ -76,7 +74,16 @@
7674
*/
7775
public abstract class AbstractJackson2HttpMessageConverter extends AbstractGenericHttpMessageConverter<Object> {
7876

79-
private static final Map<String, JsonEncoding> ENCODINGS = jsonEncodings();
77+
private static final Map<String, JsonEncoding> ENCODINGS;
78+
79+
static {
80+
ENCODINGS = new HashMap<>(JsonEncoding.values().length + 1);
81+
for (JsonEncoding encoding : JsonEncoding.values()) {
82+
ENCODINGS.put(encoding.getJavaName(), encoding);
83+
}
84+
ENCODINGS.put("US-ASCII", JsonEncoding.UTF8);
85+
}
86+
8087

8188
/**
8289
* The default charset used by the converter.
@@ -398,9 +405,4 @@ protected Long getContentLength(Object object, @Nullable MediaType contentType)
398405
return super.getContentLength(object, contentType);
399406
}
400407

401-
private static Map<String, JsonEncoding> jsonEncodings() {
402-
return EnumSet.allOf(JsonEncoding.class).stream()
403-
.collect(Collectors.toMap(JsonEncoding::getJavaName, Function.identity()));
404-
}
405-
406408
}

spring-web/src/test/java/org/springframework/http/codec/json/Jackson2JsonDecoderTests.java

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ public void canDecode() {
9191
assertFalse(decoder.canDecode(forClass(Pojo.class), APPLICATION_XML));
9292
assertTrue(this.decoder.canDecode(forClass(Pojo.class),
9393
new MediaType("application", "json", StandardCharsets.UTF_8)));
94+
assertTrue(this.decoder.canDecode(forClass(Pojo.class),
95+
new MediaType("application", "json", StandardCharsets.US_ASCII)));
9496
assertTrue(this.decoder.canDecode(forClass(Pojo.class),
9597
new MediaType("application", "json", StandardCharsets.ISO_8859_1)));
9698

@@ -246,8 +248,7 @@ public void decodeNonUnicode() {
246248
stringBuffer("{\"føø\":\"bår\"}", StandardCharsets.ISO_8859_1)
247249
);
248250

249-
testDecode(input, ResolvableType.forType(new ParameterizedTypeReference<Map<String, String>>() {
250-
}),
251+
testDecode(input, ResolvableType.forType(new ParameterizedTypeReference<Map<String, String>>() {}),
251252
step -> step.assertNext(o -> {
252253
assertTrue(o instanceof Map);
253254
Map<String, String> map = (Map<String, String>) o;
@@ -263,8 +264,7 @@ public void decodeNonUnicode() {
263264
public void decodeMonoNonUtf8Encoding() {
264265
Mono<DataBuffer> input = stringBuffer("{\"foo\":\"bar\"}", StandardCharsets.UTF_16);
265266

266-
testDecodeToMono(input, ResolvableType.forType(new ParameterizedTypeReference<Map<String, String>>() {
267-
}),
267+
testDecodeToMono(input, ResolvableType.forType(new ParameterizedTypeReference<Map<String, String>>() {}),
268268
step -> step.assertNext(o -> {
269269
Map<String, String> map = (Map<String, String>) o;
270270
assertEquals("bar", map.get("foo"));
@@ -274,6 +274,24 @@ public void decodeMonoNonUtf8Encoding() {
274274
null);
275275
}
276276

277+
@Test
278+
@SuppressWarnings("unchecked")
279+
public void decodeAscii() {
280+
Flux<DataBuffer> input = Flux.concat(
281+
stringBuffer("{\"foo\":\"bar\"}", StandardCharsets.US_ASCII)
282+
);
283+
284+
testDecode(input, ResolvableType.forType(new ParameterizedTypeReference<Map<String, String>>() {}),
285+
step -> step.assertNext(o -> {
286+
Map<String, String> map = (Map<String, String>) o;
287+
assertEquals("bar", map.get("foo"));
288+
})
289+
.verifyComplete(),
290+
MediaType.parseMediaType("application/json; charset=us-ascii"),
291+
null);
292+
}
293+
294+
277295
private Mono<DataBuffer> stringBuffer(String value) {
278296
return stringBuffer(value, StandardCharsets.UTF_8);
279297
}

spring-web/src/test/java/org/springframework/http/codec/json/Jackson2JsonEncoderTests.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@
4242
import org.springframework.util.MimeTypeUtils;
4343

4444
import static java.util.Collections.singletonMap;
45-
import static org.junit.Assert.*;
45+
import static org.junit.Assert.assertEquals;
46+
import static org.junit.Assert.assertFalse;
47+
import static org.junit.Assert.assertTrue;
4648
import static org.springframework.http.MediaType.APPLICATION_JSON;
4749
import static org.springframework.http.MediaType.APPLICATION_JSON_UTF8;
4850
import static org.springframework.http.MediaType.APPLICATION_OCTET_STREAM;
@@ -73,6 +75,8 @@ public void canEncode() {
7375

7476
assertTrue(this.encoder.canEncode(ResolvableType.forClass(Pojo.class),
7577
new MediaType("application", "json", StandardCharsets.UTF_8)));
78+
assertTrue(this.encoder.canEncode(ResolvableType.forClass(Pojo.class),
79+
new MediaType("application", "json", StandardCharsets.US_ASCII)));
7680
assertFalse(this.encoder.canEncode(ResolvableType.forClass(Pojo.class),
7781
new MediaType("application", "json", StandardCharsets.ISO_8859_1)));
7882

@@ -223,6 +227,17 @@ public void encodeWithFlushAfterWriteOff() {
223227
.verify(Duration.ofSeconds(5));
224228
}
225229

230+
@Test
231+
public void encodeAscii() {
232+
Mono<Object> input = Mono.just(new Pojo("foo", "bar"));
233+
234+
testEncode(input, ResolvableType.forClass(Pojo.class), step -> step
235+
.consumeNextWith(expectString("{\"foo\":\"foo\",\"bar\":\"bar\"}"))
236+
.verifyComplete(),
237+
new MimeType("application", "json", StandardCharsets.US_ASCII), null);
238+
239+
}
240+
226241

227242
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
228243
private static class ParentClass {

spring-web/src/test/java/org/springframework/http/converter/json/MappingJackson2HttpMessageConverterTests.java

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,14 @@
4343
import org.springframework.http.converter.HttpMessageNotReadableException;
4444
import org.springframework.lang.Nullable;
4545

46-
import static org.hamcrest.CoreMatchers.*;
47-
import static org.junit.Assert.*;
46+
import static org.hamcrest.CoreMatchers.containsString;
47+
import static org.hamcrest.CoreMatchers.not;
48+
import static org.junit.Assert.assertArrayEquals;
49+
import static org.junit.Assert.assertEquals;
50+
import static org.junit.Assert.assertFalse;
51+
import static org.junit.Assert.assertThat;
52+
import static org.junit.Assert.assertTrue;
53+
import static org.junit.Assert.fail;
4854

4955
/**
5056
* Jackson 2.x converter tests.
@@ -65,6 +71,7 @@ public void canRead() {
6571
assertTrue(converter.canRead(MyBean.class, new MediaType("application", "json")));
6672
assertTrue(converter.canRead(Map.class, new MediaType("application", "json")));
6773
assertTrue(converter.canRead(MyBean.class, new MediaType("application", "json", StandardCharsets.UTF_8)));
74+
assertTrue(converter.canRead(MyBean.class, new MediaType("application", "json", StandardCharsets.US_ASCII)));
6875
assertTrue(converter.canRead(MyBean.class, new MediaType("application", "json", StandardCharsets.ISO_8859_1)));
6976
}
7077

@@ -73,6 +80,7 @@ public void canWrite() {
7380
assertTrue(converter.canWrite(MyBean.class, new MediaType("application", "json")));
7481
assertTrue(converter.canWrite(Map.class, new MediaType("application", "json")));
7582
assertTrue(converter.canWrite(MyBean.class, new MediaType("application", "json", StandardCharsets.UTF_8)));
83+
assertTrue(converter.canWrite(MyBean.class, new MediaType("application", "json", StandardCharsets.US_ASCII)));
7684
assertFalse(converter.canWrite(MyBean.class, new MediaType("application", "json", StandardCharsets.ISO_8859_1)));
7785
}
7886

@@ -465,6 +473,34 @@ public void readNonUnicode() throws Exception {
465473
assertEquals("bår", result.get("føø"));
466474
}
467475

476+
@Test
477+
@SuppressWarnings("unchecked")
478+
public void readAscii() throws Exception {
479+
String body = "{\"foo\":\"bar\"}";
480+
Charset charset = StandardCharsets.US_ASCII;
481+
MockHttpInputMessage inputMessage = new MockHttpInputMessage(body.getBytes(charset));
482+
inputMessage.getHeaders().setContentType(new MediaType("application", "json", charset));
483+
HashMap<String, Object> result = (HashMap<String, Object>) this.converter.read(HashMap.class, inputMessage);
484+
485+
assertEquals(1, result.size());
486+
assertEquals("bar", result.get("foo"));
487+
}
488+
489+
@Test
490+
@SuppressWarnings("unchecked")
491+
public void writeAscii() throws Exception {
492+
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
493+
Map<String,Object> body = new HashMap<>();
494+
body.put("foo", "bar");
495+
Charset charset = StandardCharsets.US_ASCII;
496+
MediaType contentType = new MediaType("application", "json", charset);
497+
converter.write(body, contentType, outputMessage);
498+
499+
String result = outputMessage.getBodyAsString(charset);
500+
assertEquals("{\"foo\":\"bar\"}", result);
501+
assertEquals(contentType, outputMessage.getHeaders().getContentType());
502+
}
503+
468504

469505
interface MyInterface {
470506

0 commit comments

Comments
 (0)