Skip to content

Commit d30ed95

Browse files
committed
Merge branch '3.x' of github.com:FasterXML/jackson-core into 3.x
2 parents ee9dc0f + c035b52 commit d30ed95

File tree

13 files changed

+1332
-4
lines changed

13 files changed

+1332
-4
lines changed

release-notes/CREDITS

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ Sven Döring (@sdoeringNew)
1414
* Contributed #680: Allow use of `java.nio.file.Path` as parser source, generator target
1515
(3.0.0)
1616

17+
@sri-adarsh-kumar
18+
* Contributed #1288: Add new method like `JsonParser.readText(Writer)` (and
19+
implementation) for truly non-buffering reads
20+
(3.1.0)
21+
1722
Nicholas Rayburn (@nrayburn-tech)
1823
* Contributed #1514: Additional configuration to closer match Jackson 2 behavior
1924
(3.1.0)

release-notes/VERSION

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ JSON library.
1717

1818
3.1.0 (not yet released)
1919

20+
#1288: Add new method like `JsonParser.readText(Writer)` (and
21+
implementation) for truly non-buffering reads
22+
(contributed by @sri-adarsh-kumar)
2023
#1548: `StreamReadConstraints.maxDocumentLength` not checked when
2124
creating parser with fixed buffer
2225
#1553: Max Depth validation not working for `DataInput`-backed `JsonParser`s

src/main/java/tools/jackson/core/JsonParser.java

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -884,8 +884,9 @@ public Boolean nextBooleanValue() throws JacksonException {
884884
* other {@code getString()} calls (that is, it will not be consumed).
885885
* So this accessor only avoids construction of {@link java.lang.String}
886886
* compared to plain {@link #getString()} method.
887+
* Note there is method {@link #readString(Writer)} that may avoid buffering.
887888
*<p>
888-
* NOTE: In Jackson 2.x this method was called {@code getString(Writer)}.
889+
* NOTE: In Jackson 2.x this method was called {@code getText(Writer)}.
889890
*
890891
* @param writer Writer to write String value to
891892
*
@@ -896,6 +897,58 @@ public Boolean nextBooleanValue() throws JacksonException {
896897
*/
897898
public abstract int getString(Writer writer) throws JacksonException;
898899

900+
/**
901+
* Method to read the textual representation of the current {@link JsonToken#VALUE_STRING}
902+
* token in chunks and stream it directly to the given Writer, without buffering the entire
903+
* String value in memory. Functionally same as calling:
904+
*<pre>
905+
* writer.write(parser.getString());
906+
*</pre>
907+
* but tries to stream the decoded content directly without storing it in {@link TextBuffer},
908+
* making it suitable for arbitrarily large strings without memory constraints.
909+
*<p>
910+
* NOTE: whether streaming happens depends on format-specific implementation of
911+
* this method -- the default implementation delegates to
912+
* {@link #getString(Writer)} which does not stream content.
913+
*<p>
914+
* NOTE: This method <b>consumes</b> the contents of the {@link JsonToken#VALUE_STRING}
915+
* token, advancing the parser state so that the underlying String value is <b>no longer
916+
* available</b> via {@link #getString()} or other {@code getString*} accessors after
917+
* this method completes. This differs from {@link #getString(Writer)} which preserves
918+
* the buffered string for subsequent access.
919+
*<p>
920+
* NOTE: This method is primarily intended for very large JSON string values (megabytes
921+
* or larger) where full buffering would be prohibitive. For typical string sizes, prefer
922+
* {@link #getString()} or {@link #getString(Writer)} which provide more convenient access.
923+
* The implementation uses an intermediate buffer for efficient bulk writes
924+
* to the Writer.
925+
*<p>
926+
* NOTE: This method <b>does</b> enforce
927+
* {@link tools.jackson.core.StreamReadConstraints#getMaxStringLength()} validation during
928+
* streaming, checking the string length at buffer boundaries and at completion. Strings
929+
* exceeding the configured limit will result in a {@link tools.jackson.core.exc.StreamConstraintsException}.
930+
*<p>
931+
* NOTE: This method is <b>NOT supported</b> by non-blocking (async) parsers and will
932+
* throw {@link UnsupportedOperationException} if called on such parsers, since content
933+
* availability is unpredictable in asynchronous parsing mode.
934+
*
935+
* @param writer Writer to stream the String value to
936+
*
937+
* @return The number of characters written to the Writer (as {@code long} to support
938+
* strings exceeding {@code Integer.MAX_VALUE})
939+
*
940+
* @throws JacksonIOException for low-level read issues, or failed write using {@link Writer}
941+
* @throws tools.jackson.core.exc.StreamReadException for decoding problems
942+
* @throws tools.jackson.core.exc.StreamConstraintsException if string length exceeds
943+
* {@link tools.jackson.core.StreamReadConstraints#getMaxStringLength()}
944+
*
945+
* @since 3.1
946+
*/
947+
public long readString(Writer writer) throws JacksonException {
948+
// Default implementation simply buffers it all
949+
return getString(writer);
950+
}
951+
899952
/**
900953
* Method similar to {@link #getString()}, but that will return
901954
* underlying (unmodifiable) character array that contains

src/main/java/tools/jackson/core/StreamReadConstraints.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,9 @@ public Builder maxNumberLength(final int maxNumLen) {
226226
*<p>
227227
* NOTE: Jackson 2.15.0 initially used a lower setting ({@code 5,000,000}); and versions
228228
* up to 3.0 {@code 20,000,000}.
229+
* Jackson 3.1 further raised this to {@code 100,000,000}.
230+
*<p>
231+
* NOTE: Setting this to {@link Integer#MAX_VALUE} effectively <b>DISABLES</b> limit.
229232
*
230233
* @param maxStringLen the maximum string length (in chars or bytes, depending on input context)
231234
*
@@ -575,6 +578,27 @@ public void validateStringLength(int length) throws StreamConstraintsException
575578
}
576579
}
577580

581+
/**
582+
* Variant of {@link #validateStringLength} but takes {@code long} instead
583+
* {@code int}.
584+
*<p>
585+
* NOTE: value of {@link Integer#MAX_VALUE} is considered special value
586+
* meaning "no limit".
587+
*
588+
* @param length Length to validate against limit
589+
*
590+
* @throws StreamConstraintsException
591+
*/
592+
public void validateStringLengthLong(long length) throws StreamConstraintsException
593+
{
594+
if (length > _maxStringLen && _maxStringLen != Integer.MAX_VALUE) {
595+
throw _constructException(
596+
"String value length (%d) exceeds the maximum allowed (%d, from %s)",
597+
length, _maxStringLen,
598+
_constrainRef("getMaxStringLength"));
599+
}
600+
}
601+
578602
/**
579603
* Convenience method that can be used to verify that a name
580604
* of specified length does not exceed maximum specific by this

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

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,26 @@ public abstract class JsonParserBase
6969
*/
7070
protected boolean _nameCopied;
7171

72+
/**
73+
* Lazily-allocated intermediate buffer used by {@code _streamString()}
74+
* implementations to batch writes to the target {@link java.io.Writer}.
75+
* Allocated on first call and reused on subsequent calls to avoid
76+
* repeated allocation for parsers that call {@code readString(Writer)}
77+
* multiple times.
78+
*
79+
* @since 3.1
80+
*/
81+
private char[] _streamStringBuffer;
82+
7283
/*
7384
/**********************************************************************
7485
/* Life-cycle
7586
/**********************************************************************
7687
*/
7788

7889
protected JsonParserBase(ObjectReadContext readCtxt,
79-
IOContext ctxt, int streamReadFeatures, int formatReadFeatures) {
90+
IOContext ctxt, int streamReadFeatures, int formatReadFeatures)
91+
{
8092
super(readCtxt, ctxt, streamReadFeatures);
8193
_formatReadFeatures = formatReadFeatures;
8294
DupDetector dups = StreamReadFeature.STRICT_DUPLICATE_DETECTION.enabledIn(streamReadFeatures)
@@ -339,6 +351,21 @@ protected char[] currentNameInBuffer() {
339351
return _nameCopyBuffer;
340352
}
341353

354+
/**
355+
* Returns the lazily-allocated intermediate buffer used by
356+
* {@code _streamString()} to batch-write decoded characters to a
357+
* {@link java.io.Writer}. The same buffer is reused across calls.
358+
*
359+
* @since 3.1
360+
*/
361+
protected char[] _bufferForStringStreaming() {
362+
char[] buf = _streamStringBuffer;
363+
if (buf == null) {
364+
_streamStringBuffer = buf = new char[1024];
365+
}
366+
return buf;
367+
}
368+
342369
/*
343370
/**********************************************************************
344371
/* Internal/package methods: Error reporting

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

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public class ReaderBasedJsonParser
4747
protected char[] _inputBuffer;
4848

4949
/**
50-
* Flag that indicates whether the input buffer is recycable (and
50+
* Flag that indicates whether the input buffer is recyclable (and
5151
* needs to be returned to recycler once we are done) or not.
5252
*<p>
5353
* If it is not, it also means that parser CANNOT modify underlying
@@ -338,6 +338,24 @@ public int getString(Writer writer) throws JacksonException
338338
return 0;
339339
}
340340

341+
@Override
342+
public long readString(Writer writer) throws JacksonException
343+
{
344+
if (_currToken == JsonToken.VALUE_STRING) {
345+
try {
346+
if (_tokenIncomplete) {
347+
return _streamString(writer);
348+
}
349+
long len = _textBuffer.contentsToWriter(writer);
350+
_textBuffer.resetWithEmpty();
351+
return len;
352+
} catch (IOException e) {
353+
throw _wrapIOFailure(e);
354+
}
355+
}
356+
return getString(writer);
357+
}
358+
341359
// // // Let's override default impls for improved performance
342360

343361
@Override
@@ -2268,6 +2286,89 @@ protected final void _skipString() throws JacksonException
22682286
}
22692287
}
22702288

2289+
// @since 3.1
2290+
private long _streamString(Writer writer)
2291+
throws IOException, JacksonException
2292+
{
2293+
_tokenIncomplete = false;
2294+
2295+
long totalLen = 0;
2296+
int inPtr = _inputPtr;
2297+
int inLen = _inputEnd;
2298+
char[] inBuf = _inputBuffer;
2299+
final long maxStringLen = _streamReadConstraints.getMaxStringLength();
2300+
2301+
// Intermediate buffer for bulk writes to improve performance (reused across calls)
2302+
char[] outBuf = _bufferForStringStreaming();
2303+
int outPtr = 0;
2304+
2305+
while (true) {
2306+
// Flush intermediate buffer when full
2307+
if (outPtr >= outBuf.length) {
2308+
writer.write(outBuf, 0, outPtr);
2309+
totalLen += outPtr;
2310+
outPtr = 0;
2311+
// Check constraints only at flush boundaries
2312+
if (totalLen > maxStringLen) {
2313+
_streamReadConstraints.validateStringLengthLong(totalLen);
2314+
}
2315+
}
2316+
2317+
if (inPtr >= inLen) {
2318+
// Flush intermediate buffer before loading more
2319+
if (outPtr > 0) {
2320+
writer.write(outBuf, 0, outPtr);
2321+
totalLen += outPtr;
2322+
outPtr = 0;
2323+
// Check constraints at input buffer boundary
2324+
if (totalLen > maxStringLen) {
2325+
_streamReadConstraints.validateStringLengthLong(totalLen);
2326+
}
2327+
}
2328+
_inputPtr = inPtr;
2329+
if (!_loadMore()) {
2330+
_reportInvalidEOF(": was expecting closing quote for a string value",
2331+
JsonToken.VALUE_STRING);
2332+
}
2333+
inPtr = _inputPtr;
2334+
inLen = _inputEnd;
2335+
}
2336+
char c = inBuf[inPtr++];
2337+
int i = c;
2338+
if (i <= INT_BACKSLASH) {
2339+
if (i == INT_BACKSLASH) {
2340+
_inputPtr = inPtr;
2341+
c = _decodeEscaped();
2342+
inPtr = _inputPtr;
2343+
inLen = _inputEnd;
2344+
} else if (i <= INT_QUOTE) {
2345+
if (i == INT_QUOTE) {
2346+
_inputPtr = inPtr;
2347+
break;
2348+
}
2349+
if (i < INT_SPACE) {
2350+
_inputPtr = inPtr;
2351+
_throwUnquotedSpace(i, "string value");
2352+
}
2353+
}
2354+
}
2355+
// Accumulate character in intermediate buffer
2356+
outBuf[outPtr++] = c;
2357+
}
2358+
2359+
// Final flush of remaining characters
2360+
if (outPtr > 0) {
2361+
writer.write(outBuf, 0, outPtr);
2362+
totalLen += outPtr;
2363+
// Validate final string length
2364+
if (totalLen > maxStringLen) {
2365+
_streamReadConstraints.validateStringLengthLong(totalLen);
2366+
}
2367+
}
2368+
_textBuffer.resetWithEmpty();
2369+
return totalLen;
2370+
}
2371+
22712372
/*
22722373
/**********************************************************************
22732374
/* Internal methods, other parsing

0 commit comments

Comments
 (0)