Skip to content

Commit eb112e6

Browse files
authored
Fix #1015: JsonFactory always respects CANONICALIZE_FIELD_NAMES (#1016)
Previously a subset of methods did not check for `CANONICALIZE_FIELD_NAMES` in the factory features configuration.
1 parent 315ac5a commit eb112e6

File tree

4 files changed

+74
-3
lines changed

4 files changed

+74
-3
lines changed

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1299,7 +1299,7 @@ public JsonParser createNonBlockingByteArrayParser() throws IOException
12991299
// for non-JSON input:
13001300
_requireJSONFactory("Non-blocking source not (yet?) supported for this format (%s)");
13011301
IOContext ctxt = _createNonBlockingContext(null);
1302-
ByteQuadsCanonicalizer can = _byteSymbolCanonicalizer.makeChild(_factoryFeatures);
1302+
ByteQuadsCanonicalizer can = _byteSymbolCanonicalizer.makeChildOrPlaceholder(_factoryFeatures);
13031303
return new NonBlockingJsonParser(ctxt, _parserFeatures, can);
13041304
}
13051305

@@ -1326,7 +1326,7 @@ public JsonParser createNonBlockingByteBufferParser() throws IOException
13261326
// for non-JSON input:
13271327
_requireJSONFactory("Non-blocking source not (yet?) supported for this format (%s)");
13281328
IOContext ctxt = _createNonBlockingContext(null);
1329-
ByteQuadsCanonicalizer can = _byteSymbolCanonicalizer.makeChild(_factoryFeatures);
1329+
ByteQuadsCanonicalizer can = _byteSymbolCanonicalizer.makeChildOrPlaceholder(_factoryFeatures);
13301330
return new NonBlockingByteBufferJsonParser(ctxt, _parserFeatures, can);
13311331
}
13321332

@@ -1849,7 +1849,7 @@ protected JsonParser _createParser(DataInput input, IOContext ctxt) throws IOExc
18491849
// Also: while we can't do full bootstrapping (due to read-ahead limitations), should
18501850
// at least handle possible UTF-8 BOM
18511851
int firstByte = ByteSourceJsonBootstrapper.skipUTF8BOM(input);
1852-
ByteQuadsCanonicalizer can = _byteSymbolCanonicalizer.makeChild(_factoryFeatures);
1852+
ByteQuadsCanonicalizer can = _byteSymbolCanonicalizer.makeChildOrPlaceholder(_factoryFeatures);
18531853
return new UTF8DataInputJsonParser(ctxt, _parserFeatures, input,
18541854
_objectCodec, can, firstByte);
18551855
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1933,6 +1933,10 @@ private final String addName(int[] quads, int qlen, int lastQuadBytes)
19331933

19341934
// Ok. Now we have the character array, and can construct the String
19351935
String baseName = new String(cbuf, 0, cix);
1936+
// 5-May-2023, ckozak: [core#1015] respect CANONICALIZE_FIELD_NAMES factory config.
1937+
if (!_symbols.isCanonicalizing()) {
1938+
return baseName;
1939+
}
19361940
// And finally, un-align if necessary
19371941
if (lastQuadBytes < 4) {
19381942
quads[qlen-1] = lastQuad;

src/main/java/com/fasterxml/jackson/core/json/async/NonBlockingJsonParserBase.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -790,6 +790,10 @@ protected final String _addName(int[] quads, int qlen, int lastQuadBytes)
790790

791791
// Ok. Now we have the character array, and can construct the String
792792
String baseName = new String(cbuf, 0, cix);
793+
// 5-May-2023, ckozak: [core#1015] respect CANONICALIZE_FIELD_NAMES factory config.
794+
if (!_symbols.isCanonicalizing()) {
795+
return baseName;
796+
}
793797
// And finally, un-align if necessary
794798
if (lastQuadBytes < 4) {
795799
quads[qlen-1] = lastQuad;

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

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package com.fasterxml.jackson.core.json;
22

33
import java.io.*;
4+
import java.nio.charset.StandardCharsets;
45
import java.util.Iterator;
56

67
import com.fasterxml.jackson.core.*;
8+
import com.fasterxml.jackson.core.json.async.NonBlockingJsonParser;
79
import com.fasterxml.jackson.core.type.ResolvedType;
810
import com.fasterxml.jackson.core.type.TypeReference;
911

@@ -288,4 +290,65 @@ public void testRootValues() throws Exception
288290
g.close();
289291
assertEquals("1/2/3", w.toString());
290292
}
293+
294+
public void testCanonicalizationEnabled() throws Exception {
295+
doCanonicalizationTest(false);
296+
}
297+
298+
public void testCanonicalizationDisabled() throws Exception {
299+
doCanonicalizationTest(false);
300+
}
301+
302+
// Configure the JsonFactory as expected, and verify across common shapes of input
303+
// to cover common JsonParser implementations.
304+
private void doCanonicalizationTest(boolean canonicalize) throws Exception {
305+
String contents = "{\"a\":true,\"a\":true}";
306+
JsonFactory factory = JsonFactory.builder()
307+
.configure(JsonFactory.Feature.CANONICALIZE_FIELD_NAMES, canonicalize)
308+
.build();
309+
try (JsonParser parser = factory.createParser(contents)) {
310+
verifyCanonicalizationTestResult(parser, canonicalize);
311+
}
312+
try (JsonParser parser = factory.createParser(contents.getBytes(StandardCharsets.UTF_8))) {
313+
verifyCanonicalizationTestResult(parser, canonicalize);
314+
}
315+
try (JsonParser parser = factory.createParser(
316+
new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8)))) {
317+
verifyCanonicalizationTestResult(parser, canonicalize);
318+
}
319+
try (JsonParser parser = factory.createParser(new StringReader(contents))) {
320+
verifyCanonicalizationTestResult(parser, canonicalize);
321+
}
322+
try (JsonParser parser = factory.createParser((DataInput) new DataInputStream(
323+
new ByteArrayInputStream(contents.getBytes(StandardCharsets.UTF_8))))) {
324+
verifyCanonicalizationTestResult(parser, canonicalize);
325+
}
326+
try (NonBlockingJsonParser parser = (NonBlockingJsonParser) factory.createNonBlockingByteArrayParser()) {
327+
byte[] data = contents.getBytes(StandardCharsets.UTF_8);
328+
parser.feedInput(data, 0, data.length);
329+
parser.endOfInput();
330+
verifyCanonicalizationTestResult(parser, canonicalize);
331+
}
332+
}
333+
334+
private void verifyCanonicalizationTestResult(JsonParser parser, boolean canonicalize) throws Exception {
335+
assertToken(JsonToken.START_OBJECT, parser.nextToken());
336+
String field1 = parser.nextFieldName();
337+
assertEquals("a", field1);
338+
assertToken(JsonToken.VALUE_TRUE, parser.nextToken());
339+
String field2 = parser.nextFieldName();
340+
assertEquals("a", field2);
341+
if (canonicalize) {
342+
assertSame(field1, field2);
343+
} else {
344+
// n.b. It's possible that this may flake if a garbage collector with string deduplication
345+
// enabled is used. Such a failure is unlikely because younger GC generations are typically
346+
// not considered for deduplication due to high churn, but under heavy memory pressure it
347+
// may be possible. I've left this comment in an attempt to simplify investigation in the
348+
// off-chance that such flakes eventually occur.
349+
assertNotSame(field1, field2);
350+
}
351+
assertToken(JsonToken.VALUE_TRUE, parser.nextToken());
352+
assertToken(JsonToken.END_OBJECT, parser.nextToken());
353+
}
291354
}

0 commit comments

Comments
 (0)