Skip to content

Commit 86faf57

Browse files
committed
Merge branch '2.17'
2 parents d2330f8 + 25b9353 commit 86faf57

File tree

7 files changed

+138
-6
lines changed

7 files changed

+138
-6
lines changed

release-notes/CREDITS-2.x

+3
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,9 @@ Armin Samii (@artoonie)
395395
Joo Hyuk Kim (@JooHyukKim)
396396
* Contributed #1067: Add `ErrorReportConfiguration`
397397
(2.16.0)
398+
* Contributed #507: Add `JsonWriteFeature.ESCAPE_FORWARD_SLASHES`
399+
to allow escaping of '/' for String values
400+
(2.17.0)
398401

399402
David Schlosnagle (@schlosna)
400403
* Contributed #1081: Make `ByteSourceJsonBootstrapper` use `StringReader` for < 8KiB

release-notes/VERSION-2.x

+3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ a pure JSON library.
1616

1717
2.17.0 (not yet released)
1818

19+
#507: Add `JsonWriteFeature.ESCAPE_FORWARD_SLASHES` to allow escaping of '/' for
20+
String values
21+
(contributed by Joo-Hyuk K)
1922
#1137: Improve detection of "is a NaN" to only consider explicit cases,
2023
not `double` overflow/underflow
2124
#1145: `JsonPointer.appendProperty(String)` does not escape the property name

src/main/java/tools/jackson/core/io/CharTypes.java

+50-2
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,16 @@ public final class CharTypes
173173
sOutputEscapes128 = table;
174174
}
175175

176+
/**
177+
* Lookup table same as {@link #sOutputEscapes128} except that
178+
* forward slash ('/') is also escaped
179+
*/
180+
protected final static int[] sOutputEscapes128WithSlash;
181+
static {
182+
sOutputEscapes128WithSlash = Arrays.copyOf(sOutputEscapes128, sOutputEscapes128.length);
183+
sOutputEscapes128WithSlash['/'] = '/';
184+
}
185+
176186
/**
177187
* Lookup table for the first 256 Unicode characters (ASCII / UTF-8)
178188
* range. For actual hex digits, contains corresponding value;
@@ -228,6 +238,28 @@ public static int[] get7BitOutputEscapes(int quoteChar) {
228238
return AltEscapes.instance.escapesFor(quoteChar);
229239
}
230240

241+
/**
242+
* Alternative to {@link #get7BitOutputEscapes()} when either a non-standard
243+
* quote character is used, or forward slash is to be escaped.
244+
*
245+
* @param quoteChar Character used for quoting textual values and property names;
246+
* usually double-quote but sometimes changed to single-quote (apostrophe)
247+
* @param escapeSlash
248+
*
249+
* @return 128-entry {@code int[]} that contains escape definitions
250+
*
251+
* @since 2.17
252+
*/
253+
public static int[] get7BitOutputEscapes(int quoteChar, boolean escapeSlash) {
254+
if (quoteChar == '"') {
255+
if (escapeSlash) {
256+
return sOutputEscapes128WithSlash;
257+
}
258+
return sOutputEscapes128;
259+
}
260+
return AltEscapes.instance.escapesFor(quoteChar, escapeSlash);
261+
}
262+
231263
public static int charToHex(int ch)
232264
{
233265
// 08-Nov-2019, tatu: As per [core#540] and [core#578], changed to
@@ -241,7 +273,6 @@ public static char hexToChar(int ch)
241273
return HC[ch];
242274
}
243275

244-
245276
/**
246277
* Helper method for appending JSON-escaped version of contents
247278
* into specific {@link StringBuilder}, using default JSON specification
@@ -301,6 +332,9 @@ private static class AltEscapes {
301332

302333
private int[][] _altEscapes = new int[128][];
303334

335+
// @since 2.17
336+
private int[][] _altEscapesWithSlash = new int[128][];
337+
304338
public int[] escapesFor(int quoteChar) {
305339
int[] esc = _altEscapes[quoteChar];
306340
if (esc == null) {
@@ -313,6 +347,20 @@ public int[] escapesFor(int quoteChar) {
313347
}
314348
return esc;
315349
}
350+
351+
// @since 2.17
352+
public int[] escapesFor(int quoteChar, boolean escapeSlash)
353+
{
354+
if (!escapeSlash) {
355+
return escapesFor(quoteChar);
356+
}
357+
int[] esc = _altEscapesWithSlash[quoteChar];
358+
if (esc == null) {
359+
esc = escapesFor(quoteChar);
360+
esc['/'] = '/';
361+
_altEscapesWithSlash[quoteChar] = esc;
362+
}
363+
return esc;
364+
}
316365
}
317366
}
318-

src/main/java/tools/jackson/core/json/JsonWriteFeature.java

+9
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,15 @@ public enum JsonWriteFeature
100100
*/
101101
// ESCAPE_UTF8_SURROGATES(false, JsonGenerator.Feature.ESCAPE_UTF8_SURROGATES),
102102

103+
/**
104+
* Feature that specifies whether {@link JsonGenerator} should escape forward slashes.
105+
* <p>
106+
* Feature is disabled by default for Jackson 2.x version, and enabled by default in Jackson 3.0.
107+
*
108+
* @since 2.17
109+
*/
110+
ESCAPE_FORWARD_SLASHES(false),
111+
103112
;
104113

105114
final private boolean _defaultState;

src/main/java/tools/jackson/core/json/UTF8JsonGenerator.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,8 @@ public JsonGenerator setCharacterEscapes(CharacterEscapes esc)
163163
{
164164
_characterEscapes = esc;
165165
if (esc == null) {
166-
_outputEscapes = (_quoteChar == '"') ? DEFAULT_OUTPUT_ESCAPES
167-
: CharTypes.get7BitOutputEscapes(_quoteChar);
166+
_outputEscapes = CharTypes.get7BitOutputEscapes(_quoteChar,
167+
JsonWriteFeature.ESCAPE_FORWARD_SLASHES.enabledIn(_formatWriteFeatures));
168168
} else {
169169
_outputEscapes = esc.getEscapeCodesForAscii();
170170
}

src/main/java/tools/jackson/core/json/WriterBasedJsonGenerator.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,8 @@ public JsonGenerator setCharacterEscapes(CharacterEscapes esc)
112112
{
113113
_characterEscapes = esc;
114114
if (esc == null) {
115-
_outputEscapes = (_quoteChar == '"') ? DEFAULT_OUTPUT_ESCAPES
116-
: CharTypes.get7BitOutputEscapes(_quoteChar);
115+
_outputEscapes = CharTypes.get7BitOutputEscapes(_quoteChar,
116+
JsonWriteFeature.ESCAPE_FORWARD_SLASHES.enabledIn(_formatWriteFeatures));
117117
} else {
118118
_outputEscapes = esc.getEscapeCodesForAscii();
119119
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package tools.jackson.core.write;
2+
3+
import java.io.*;
4+
5+
import org.junit.jupiter.api.Test;
6+
7+
import tools.jackson.core.JsonGenerator;
8+
import tools.jackson.core.ObjectWriteContext;
9+
import tools.jackson.core.json.JsonFactory;
10+
import tools.jackson.core.json.JsonWriteFeature;
11+
12+
import static org.junit.jupiter.api.Assertions.assertEquals;
13+
14+
/**
15+
* @since 2.17
16+
*/
17+
public class JsonWriteFeatureEscapeForwardSlashTest
18+
{
19+
@Test
20+
public void testDontEscapeForwardSlash() throws Exception {
21+
final JsonFactory jsonF = JsonFactory.builder()
22+
.disable(JsonWriteFeature.ESCAPE_FORWARD_SLASHES)
23+
.build();
24+
final String expJson = "{\"url\":\"http://example.com\"}";
25+
26+
_testWithStringWriter(jsonF, expJson);
27+
_testWithByteArrayOutputStream(jsonF, expJson); // Also test with byte-backed output
28+
}
29+
30+
@Test
31+
public void testEscapeForwardSlash() throws Exception {
32+
final JsonFactory jsonF = JsonFactory.builder()
33+
.enable(JsonWriteFeature.ESCAPE_FORWARD_SLASHES)
34+
.build();
35+
final String expJson = "{\"url\":\"http:\\/\\/example.com\"}";
36+
37+
_testWithStringWriter(jsonF, expJson);
38+
_testWithByteArrayOutputStream(jsonF, expJson); // Also test with byte-backed output
39+
}
40+
41+
private void _testWithStringWriter(JsonFactory jsonF, String expJson) throws Exception {
42+
// Given
43+
Writer jsonWriter = new StringWriter();
44+
// When
45+
try (JsonGenerator generator = jsonF.createGenerator(ObjectWriteContext.empty(), jsonWriter)) {
46+
_writeDoc(generator);
47+
}
48+
// Then
49+
assertEquals(expJson, jsonWriter.toString());
50+
}
51+
52+
private void _testWithByteArrayOutputStream(JsonFactory jsonF, String expJson) throws Exception {
53+
// Given
54+
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
55+
// When
56+
try (JsonGenerator generator = jsonF.createGenerator(ObjectWriteContext.empty(), bytes)) {
57+
_writeDoc(generator);
58+
}
59+
// Then
60+
assertEquals(expJson, bytes.toString());
61+
}
62+
63+
private void _writeDoc(JsonGenerator generator) throws Exception
64+
{
65+
generator.writeStartObject(); // start object
66+
generator.writeStringProperty("url", "http://example.com");
67+
generator.writeEndObject(); // end object
68+
}
69+
}

0 commit comments

Comments
 (0)