Skip to content

Commit 04bba39

Browse files
committed
Fix #464
1 parent 4427116 commit 04bba39

File tree

6 files changed

+101
-16
lines changed

6 files changed

+101
-16
lines changed

release-notes/VERSION-2.x

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ JSON library.
1717
2.10.0 (not yet released)
1818

1919
#433: Add Builder pattern for creating configured Stream factories
20+
#464: Add "maximum unescaped char" configuration option for `JsonFactory` via builder
2021
#467: Create `JsonReadFeature` to move JSON-specific `JsonParser.Feature`s to
2122
#480: `SerializableString` value can not directly render to Writer
2223
(requested by Philippe M)

src/main/java/com/fasterxml/jackson/core/JsonFactory.java

+21-6
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,17 @@ public static int collectDefaults() {
265265
* @since 2.1
266266
*/
267267
protected SerializableString _rootValueSeparator = DEFAULT_ROOT_VALUE_SEPARATOR;
268-
268+
269+
/**
270+
* Optional threshold used for automatically escaping character above certain character
271+
* code value: either {@code 0} to indicate that no threshold is specified, or value
272+
* at or above 127 to indicate last character code that is NOT automatically escaped
273+
* (but depends on other configuration rules for checking).
274+
*
275+
* @since 2.10
276+
*/
277+
protected int _maximumNonEscapedChar;
278+
269279
/*
270280
/**********************************************************
271281
/* Construction
@@ -301,11 +311,7 @@ protected JsonFactory(JsonFactory src, ObjectCodec codec)
301311
_inputDecorator = src._inputDecorator;
302312
_outputDecorator = src._outputDecorator;
303313
_rootValueSeparator = src._rootValueSeparator;
304-
305-
/* 27-Apr-2013, tatu: How about symbol table; should we try to
306-
* reuse shared symbol tables? Could be more efficient that way;
307-
* although can slightly add to concurrency overhead.
308-
*/
314+
_maximumNonEscapedChar = src._maximumNonEscapedChar;
309315
}
310316

311317
/**
@@ -317,6 +323,7 @@ public JsonFactory(JsonFactoryBuilder b) {
317323
this(b, false);
318324
_characterEscapes = b._characterEscapes;
319325
_rootValueSeparator = b._rootValueSeparator;
326+
_maximumNonEscapedChar = b._maximumNonEscapedChar;
320327
}
321328

322329
/**
@@ -334,6 +341,8 @@ protected JsonFactory(TSFBuilder<?,?> b, boolean bogus) {
334341
_generatorFeatures = b._streamWriteFeatures;
335342
_inputDecorator = b._inputDecorator;
336343
_outputDecorator = b._outputDecorator;
344+
// NOTE: missing _maximumNonEscapedChar since that's only in JsonFactoryBuilder
345+
_maximumNonEscapedChar = 0;
337346
}
338347

339348
/**
@@ -1524,6 +1533,9 @@ protected JsonGenerator _createGenerator(Writer out, IOContext ctxt) throws IOEx
15241533
{
15251534
WriterBasedJsonGenerator gen = new WriterBasedJsonGenerator(ctxt,
15261535
_generatorFeatures, _objectCodec, out);
1536+
if (_maximumNonEscapedChar > 0) {
1537+
gen.setHighestNonEscapedChar(_maximumNonEscapedChar);
1538+
}
15271539
if (_characterEscapes != null) {
15281540
gen.setCharacterEscapes(_characterEscapes);
15291541
}
@@ -1547,6 +1559,9 @@ protected JsonGenerator _createGenerator(Writer out, IOContext ctxt) throws IOEx
15471559
protected JsonGenerator _createUTF8Generator(OutputStream out, IOContext ctxt) throws IOException {
15481560
UTF8JsonGenerator gen = new UTF8JsonGenerator(ctxt,
15491561
_generatorFeatures, _objectCodec, out);
1562+
if (_maximumNonEscapedChar > 0) {
1563+
gen.setHighestNonEscapedChar(_maximumNonEscapedChar);
1564+
}
15501565
if (_characterEscapes != null) {
15511566
gen.setCharacterEscapes(_characterEscapes);
15521567
}

src/main/java/com/fasterxml/jackson/core/JsonFactoryBuilder.java

+33-3
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,19 @@ public class JsonFactoryBuilder extends TSFBuilder<JsonFactory, JsonFactoryBuild
1818

1919
protected SerializableString _rootValueSeparator;
2020

21+
protected int _maximumNonEscapedChar;
22+
2123
public JsonFactoryBuilder() {
2224
super();
2325
_rootValueSeparator = JsonFactory.DEFAULT_ROOT_VALUE_SEPARATOR;
26+
_maximumNonEscapedChar = 0;
2427
}
2528

2629
public JsonFactoryBuilder(JsonFactory base) {
2730
super(base);
2831
_characterEscapes = base.getCharacterEscapes();
2932
_rootValueSeparator = base._rootValueSeparator;
33+
_maximumNonEscapedChar = base._maximumNonEscapedChar;
3034
}
3135

3236
/*
@@ -112,8 +116,8 @@ public JsonFactoryBuilder disable(JsonWriteFeature first, JsonWriteFeature... ot
112116
public JsonFactoryBuilder configure(JsonWriteFeature f, boolean state) {
113117
return state ? enable(f) : disable(f);
114118
}
115-
116-
// // // JSON-specific helper objects
119+
120+
// // // JSON-specific helper objects, settings
117121

118122
/**
119123
* Method for defining custom escapes factory uses for {@link JsonGenerator}s
@@ -135,7 +139,7 @@ public JsonFactoryBuilder rootValueSeparator(String sep) {
135139
_rootValueSeparator = (sep == null) ? null : new SerializedString(sep);
136140
return this;
137141
}
138-
142+
139143
/**
140144
* Method that allows overriding String used for separating root-level
141145
* JSON values (default is single space character)
@@ -148,9 +152,35 @@ public JsonFactoryBuilder rootValueSeparator(SerializableString sep) {
148152
return this;
149153
}
150154

155+
/**
156+
* Method that allows specifying threshold beyond which all characters are
157+
* automatically escaped (without checking possible custom escaping settings
158+
* a la {@link #characterEscapes}: for example, to force escaping of all non-ASCII
159+
* characters (set to 127), or all non-Latin-1 character (set to 255).
160+
* Default setting is "disabled", specified by passing value of {@code 0} (or
161+
* negative numbers).
162+
*<p>
163+
* NOTE! Lowest value (aside from marker 0) is 127: for ASCII range, other checks apply
164+
* and this threshold is ignored.
165+
*
166+
* @param maxNonEscaped Highest character code that is NOT automatically escaped; if
167+
* positive value above 0, or 0 to indicate that no automatic escaping is applied
168+
* beside from what JSON specification requires (and possible custom escape settings).
169+
* Values between 1 and 127 are all taken to behave as if 127 is specified: that is,
170+
* no automatic escaping is applied in ASCII range.
171+
*/
172+
public JsonFactoryBuilder highestNonEscapedChar(int maxNonEscaped) {
173+
_maximumNonEscapedChar = (maxNonEscaped <= 0) ? 0 : Math.max(127, maxNonEscaped);
174+
return this;
175+
}
176+
177+
// // // Accessors for JSON-specific settings
178+
151179
public CharacterEscapes characterEscapes() { return _characterEscapes; }
152180
public SerializableString rootValueSeparator() { return _rootValueSeparator; }
153181

182+
public int highestNonEscapedChar() { return _maximumNonEscapedChar; }
183+
154184
@Override
155185
public JsonFactory build() {
156186
// 28-Dec-2017, tatu: No special settings beyond base class ones, so:

src/main/java/com/fasterxml/jackson/core/JsonGenerator.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -511,7 +511,7 @@ public PrettyPrinter getPrettyPrinter() {
511511
* simply return 0.
512512
*
513513
* @return Currently active limitation for highest non-escaped character,
514-
* if defined; or -1 to indicate no additional escaping is performed.
514+
* if defined; or 0 to indicate no additional escaping is performed.
515515
*/
516516
public int getHighestEscapedChar() { return 0; }
517517

src/main/java/com/fasterxml/jackson/core/json/UTF8JsonGenerator.java

-2
Original file line numberDiff line numberDiff line change
@@ -1318,10 +1318,8 @@ private final void _writeStringSegment(char[] cbuf, int offset, int len)
13181318
}
13191319
_outputTail = outputPtr;
13201320
if (offset < len) {
1321-
// [JACKSON-106]
13221321
if (_characterEscapes != null) {
13231322
_writeCustomStringSegment2(cbuf, offset, len);
1324-
// [JACKSON-102]
13251323
} else if (_maximumNonEscapedChar == 0) {
13261324
_writeStringSegment2(cbuf, offset, len);
13271325
} else {

src/test/java/com/fasterxml/jackson/core/json/TestCharEscaping.java

+45-4
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
public class TestCharEscaping
1313
extends com.fasterxml.jackson.core.BaseTest
1414
{
15-
// for [JACKSON-627]
1615
@SuppressWarnings("serial")
1716
private final static CharacterEscapes ESC_627 = new CharacterEscapes() {
1817
final int[] ascii = CharacterEscapes.standardAsciiEscapesForJSON();
@@ -132,7 +131,6 @@ public void test8DigitSequence()
132131
jp.close();
133132
}
134133

135-
// for [JACKSON-627]
136134
public void testWriteLongCustomEscapes() throws Exception
137135
{
138136
JsonFactory jf = new JsonFactory();
@@ -150,7 +148,7 @@ public void testWriteLongCustomEscapes() throws Exception
150148
jgen.close();
151149
}
152150

153-
// [Issue#116]
151+
// [jackson-core#116]
154152
public void testEscapesForCharArrays() throws Exception {
155153
JsonFactory jf = new JsonFactory();
156154
StringWriter writer = new StringWriter();
@@ -160,5 +158,48 @@ public void testEscapesForCharArrays() throws Exception {
160158
jgen.close();
161159
assertEquals("\"\\u0000\"", writer.toString());
162160
}
163-
}
164161

162+
// [jackson-core#116]
163+
public void testEscapeNonLatin1Chars() throws Exception {
164+
_testEscapeNonLatin1ViaChars(false);
165+
}
166+
167+
// [jackson-core#116]
168+
public void testEscapeNonLatin1Bytes() throws Exception {
169+
_testEscapeNonLatin1ViaChars(true);
170+
}
171+
172+
private void _testEscapeNonLatin1ViaChars(boolean useBytes) throws Exception {
173+
// NOTE! First one is outside latin-1, so escape; second one within, do NOT escape:
174+
final String VALUE_IN = "Line\u2028feed, \u00D6l!";
175+
final String VALUE_ESCAPED = "Line\\u2028feed, \u00D6l!";
176+
final JsonFactory DEFAULT_F = new JsonFactory();
177+
178+
// First: with default settings, no auto-escaping
179+
_testEscapeNonLatin1(DEFAULT_F, VALUE_IN, VALUE_IN, useBytes); // char
180+
181+
// Second: with escaping beyond Latin-1 range
182+
final JsonFactory latinF = ((JsonFactoryBuilder)JsonFactory.builder())
183+
.highestNonEscapedChar(255)
184+
.build();
185+
_testEscapeNonLatin1(latinF, VALUE_IN, VALUE_ESCAPED, useBytes);
186+
}
187+
188+
private void _testEscapeNonLatin1(JsonFactory f, String valueIn, String expEncoded,
189+
boolean useBytes) throws Exception
190+
{
191+
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
192+
StringWriter sw = new StringWriter();
193+
final JsonGenerator g = useBytes ? f.createGenerator(bytes, JsonEncoding.UTF8)
194+
: f.createGenerator(sw);
195+
g.writeStartArray();
196+
g.writeString(valueIn);
197+
g.writeEndArray();
198+
g.close();
199+
200+
// Don't parse, as we want to verify actual escaping aspects
201+
202+
final String doc = useBytes ? bytes.toString("UTF-8") : sw.toString();
203+
assertEquals("[\""+expEncoded+"\"]", doc);
204+
}
205+
}

0 commit comments

Comments
 (0)