Skip to content

Commit a802536

Browse files
authored
Fixes #1200: change default for "Escape slash" to true for 3.0 (#1201)
1 parent 86faf57 commit a802536

File tree

6 files changed

+66
-60
lines changed

6 files changed

+66
-60
lines changed

release-notes/VERSION

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ JSON library.
4040
#793: Rename "com.fasterxml.jackson" -> "tools.jackson"
4141
#1090: Remove `BufferRecyclers.SYSTEM_PROPERTY_TRACK_REUSABLE_BUFFERS`
4242
#1125: Remove `TokenStreamFactory.Feature.USE_THREAD_LOCAL_FOR_BUFFER_RECYCLING`
43+
#1200: Change `JsonWriteFeature.ESCAPE_FORWARD_SLASHES` default to `true` for 3.0
4344
- Rename `JsonGenerator.Feature.AUTO_CLOSE_JSON_CONTENT` as `AUTO_CLOSE_CONTENT`
4445
- Add `TreeCodec.nullNode()`, `TreeNode.isNull()` methods
4546
- Change the way `JsonLocation.NA` is included in exception messages

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

+41-47
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ public final class CharTypes
153153
* Lookup table used for determining which output characters in
154154
* 7-bit ASCII range need to be quoted.
155155
*/
156-
protected final static int[] sOutputEscapes128;
156+
protected final static int[] sOutputEscapes128NoSlash;
157157
static {
158158
int[] table = new int[128];
159159
// Control chars need generic escape sequence
@@ -170,16 +170,16 @@ public final class CharTypes
170170
table[0x0C] = 'f';
171171
table[0x0A] = 'n';
172172
table[0x0D] = 'r';
173-
sOutputEscapes128 = table;
173+
sOutputEscapes128NoSlash = table;
174174
}
175175

176176
/**
177-
* Lookup table same as {@link #sOutputEscapes128} except that
177+
* Lookup table same as {@link #sOutputEscapes128NoSlash} except that
178178
* forward slash ('/') is also escaped
179179
*/
180180
protected final static int[] sOutputEscapes128WithSlash;
181181
static {
182-
sOutputEscapes128WithSlash = Arrays.copyOf(sOutputEscapes128, sOutputEscapes128.length);
182+
sOutputEscapes128WithSlash = Arrays.copyOf(sOutputEscapes128NoSlash, sOutputEscapes128NoSlash.length);
183183
sOutputEscapes128WithSlash['/'] = '/';
184184
}
185185

@@ -217,25 +217,13 @@ public final class CharTypes
217217
* Value of 0 means "no escaping"; other positive values that value is character
218218
* to use after backslash; and negative values that generic (backslash - u)
219219
* escaping is to be used.
220+
*<p>
221+
* NOTE: as of Jackson 3.0, forward slash ({@code "/"}) is escaped by default.
220222
*
221223
* @return 128-entry {@code int[]} that contains escape definitions
222224
*/
223-
public static int[] get7BitOutputEscapes() { return sOutputEscapes128; }
224-
225-
/**
226-
* Alternative to {@link #get7BitOutputEscapes()} when a non-standard quote character
227-
* is used.
228-
*
229-
* @param quoteChar Character used for quoting textual values and property names;
230-
* usually double-quote but sometimes changed to single-quote (apostrophe)
231-
*
232-
* @return 128-entry {@code int[]} that contains escape definitions
233-
*/
234-
public static int[] get7BitOutputEscapes(int quoteChar) {
235-
if (quoteChar == '"') {
236-
return sOutputEscapes128;
237-
}
238-
return AltEscapes.instance.escapesFor(quoteChar);
225+
public static int[] get7BitOutputEscapes() {
226+
return get7BitOutputEscapes('"', true);
239227
}
240228

241229
/**
@@ -244,7 +232,8 @@ public static int[] get7BitOutputEscapes(int quoteChar) {
244232
*
245233
* @param quoteChar Character used for quoting textual values and property names;
246234
* usually double-quote but sometimes changed to single-quote (apostrophe)
247-
* @param escapeSlash
235+
* @param escapeSlash Whether forward slash ({@code "/"}) is escaped by default
236+
* or not.
248237
*
249238
* @return 128-entry {@code int[]} that contains escape definitions
250239
*
@@ -255,9 +244,9 @@ public static int[] get7BitOutputEscapes(int quoteChar, boolean escapeSlash) {
255244
if (escapeSlash) {
256245
return sOutputEscapes128WithSlash;
257246
}
258-
return sOutputEscapes128;
247+
return sOutputEscapes128NoSlash;
259248
}
260-
return AltEscapes.instance.escapesFor(quoteChar, escapeSlash);
249+
return AltQuoteEscapes.instance.altEscapesFor(quoteChar, escapeSlash);
261250
}
262251

263252
public static int charToHex(int ch)
@@ -283,7 +272,7 @@ public static char hexToChar(int ch)
283272
* @param content Unescaped String value to append with escaping applied
284273
*/
285274
public static void appendQuoted(StringBuilder sb, String content) {
286-
final int[] escCodes = sOutputEscapes128;
275+
final int[] escCodes = sOutputEscapes128WithSlash;
287276
final int escLen = escCodes.length;
288277
for (int i = 0, len = content.length(); i < len; ++i) {
289278
char c = content.charAt(i);
@@ -327,38 +316,43 @@ public static byte[] copyHexBytes(boolean uppercase) {
327316
* table, used for escaping content that uses non-standard quote
328317
* character (usually apostrophe).
329318
*/
330-
private static class AltEscapes {
331-
public final static AltEscapes instance = new AltEscapes();
319+
private static class AltQuoteEscapes {
320+
public final static AltQuoteEscapes instance = new AltQuoteEscapes();
332321

333-
private int[][] _altEscapes = new int[128][];
322+
private int[][] _altEscapesNoSlash = new int[128][];
334323

335324
// @since 2.17
336325
private int[][] _altEscapesWithSlash = new int[128][];
337326

338-
public int[] escapesFor(int quoteChar) {
339-
int[] esc = _altEscapes[quoteChar];
327+
public int[] altEscapesFor(int quoteChar, boolean escapeSlash)
328+
{
329+
int[] esc = escapeSlash
330+
? _altEscapesWithSlash[quoteChar]
331+
: _altEscapesNoSlash[quoteChar];
340332
if (esc == null) {
341-
esc = Arrays.copyOf(sOutputEscapes128, 128);
333+
esc = escapeSlash
334+
? sOutputEscapes128WithSlash
335+
: sOutputEscapes128NoSlash;
336+
esc = Arrays.copyOf(esc, esc.length);
342337
// Only add escape setting if character does not already have it
343338
if (esc[quoteChar] == 0) {
344-
esc[quoteChar] = CharacterEscapes.ESCAPE_STANDARD;
339+
// And try to use "natural" escape for apos
340+
int quoteStyle;
341+
switch (quoteChar) {
342+
case '\'':
343+
case '"':
344+
quoteStyle = quoteChar;
345+
break;
346+
default:
347+
quoteStyle = CharacterEscapes.ESCAPE_STANDARD;
348+
}
349+
esc[quoteChar] = quoteStyle;
350+
}
351+
if (escapeSlash) {
352+
_altEscapesWithSlash[quoteChar] = esc;
353+
} else {
354+
_altEscapesNoSlash[quoteChar] = esc;
345355
}
346-
_altEscapes[quoteChar] = esc;
347-
}
348-
return esc;
349-
}
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;
362356
}
363357
return esc;
364358
}

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

+2-4
Original file line numberDiff line numberDiff line change
@@ -103,11 +103,9 @@ public enum JsonWriteFeature
103103
/**
104104
* Feature that specifies whether {@link JsonGenerator} should escape forward slashes.
105105
* <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
106+
* Feature is enabled by default in Jackson 3.0 (was disabled in 2.x).
109107
*/
110-
ESCAPE_FORWARD_SLASHES(false),
108+
ESCAPE_FORWARD_SLASHES(true),
111109

112110
;
113111

src/test/java/tools/jackson/core/json/CustomQuoteCharTest.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -75,14 +75,14 @@ public void testAposQuotingWithCharBased() throws Exception
7575
_writeObject(g, "key", "It's \"fun\"");
7676
g.close();
7777
// should escape apostrophes but not quotes?
78-
assertEquals("{'key':'It\\u0027s \\\"fun\\\"'}", w.toString());
78+
assertEquals("{'key':'It\\'s \\\"fun\\\"'}", w.toString());
7979

8080
// with Array
8181
w = new StringWriter();
8282
g = createGenerator(JSON_F, w);
8383
_writeArray(g, "It's a sin");
8484
g.close();
85-
assertEquals("['It\\u0027s a sin']", w.toString());
85+
assertEquals("['It\\'s a sin']", w.toString());
8686
}
8787

8888
public void testAposQuotingWithByteBased() throws Exception
@@ -96,14 +96,14 @@ public void testAposQuotingWithByteBased() throws Exception
9696
_writeObject(g, "key", "It's \"fun\"");
9797
g.close();
9898
// should escape apostrophes but not quotes?
99-
assertEquals("{'key':'It\\u0027s \\\"fun\\\"'}", out.toString("UTF-8"));
99+
assertEquals("{'key':'It\\'s \\\"fun\\\"'}", out.toString("UTF-8"));
100100

101101
// with Array
102102
out = new ByteArrayOutputStream();
103103
g = createGenerator(JSON_F, out);
104104
_writeArray(g, "It's a sin");
105105
g.close();
106-
assertEquals("['It\\u0027s a sin']", out.toString("UTF-8"));
106+
assertEquals("['It\\'s a sin']", out.toString("UTF-8"));
107107
}
108108

109109
private void _writeObject(JsonGenerator g, String key, String value) throws Exception {

src/test/java/tools/jackson/core/json/TestCustomEscaping.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -217,8 +217,8 @@ private void _testEscapeCustom(boolean useStream, boolean stringAsChars,
217217
JsonFactory f = JsonFactory.builder()
218218
.characterEscapes(new MyEscapes(customRepl))
219219
.build();
220-
final String STR_IN = "[abcd/"+((char) TWO_BYTE_ESCAPED)+"/"+((char) THREE_BYTE_ESCAPED)+"]";
221-
final String STR_OUT = "[\\A\\u0062c"+customRepl+"/"+TWO_BYTE_ESCAPED_STRING+"/"+THREE_BYTE_ESCAPED_STRING+"]";
220+
final String STR_IN = "[abcd-"+((char) TWO_BYTE_ESCAPED)+"-"+((char) THREE_BYTE_ESCAPED)+"]";
221+
final String STR_OUT = "[\\A\\u0062c"+customRepl+"-"+TWO_BYTE_ESCAPED_STRING+"-"+THREE_BYTE_ESCAPED_STRING+"]";
222222
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
223223
JsonGenerator g;
224224

src/test/java/tools/jackson/core/write/JsonWriteFeatureEscapeForwardSlashTest.java

+16-3
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,28 @@
77
import tools.jackson.core.JsonGenerator;
88
import tools.jackson.core.ObjectWriteContext;
99
import tools.jackson.core.json.JsonFactory;
10+
import tools.jackson.core.json.JsonGeneratorBase;
1011
import tools.jackson.core.json.JsonWriteFeature;
1112

1213
import static org.junit.jupiter.api.Assertions.assertEquals;
14+
import static org.junit.jupiter.api.Assertions.assertTrue;
1315

14-
/**
15-
* @since 2.17
16-
*/
1716
public class JsonWriteFeatureEscapeForwardSlashTest
1817
{
18+
@Test
19+
public void testDefaultSettings() {
20+
JsonFactory jsonF = new JsonFactory();
21+
assertTrue(jsonF.isEnabled(JsonWriteFeature.ESCAPE_FORWARD_SLASHES));
22+
try (JsonGeneratorBase g = (JsonGeneratorBase) jsonF.createGenerator(ObjectWriteContext.empty(),
23+
new StringWriter())) {
24+
assertTrue(g.isEnabled(JsonWriteFeature.ESCAPE_FORWARD_SLASHES));
25+
}
26+
try (JsonGeneratorBase g = (JsonGeneratorBase) jsonF.createGenerator(ObjectWriteContext.empty(),
27+
new ByteArrayOutputStream())) {
28+
assertTrue(g.isEnabled(JsonWriteFeature.ESCAPE_FORWARD_SLASHES));
29+
}
30+
}
31+
1932
@Test
2033
public void testDontEscapeForwardSlash() throws Exception {
2134
final JsonFactory jsonF = JsonFactory.builder()

0 commit comments

Comments
 (0)