Skip to content

Fix #1284: avoid buffering-as-String for JsonParser.getFloat()/getDouble()/getDecimal() #1313

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions release-notes/VERSION-2.x
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ a pure JSON library.
#1274: `NUL`-corrupted keys, values on JSON serialization
(reported, fix contributed by Jared S)
#1277: Add back Java 22 optimisation in FastDoubleParser
#1284: Optimize `JsonParser.getDoubleValue()/getFloatValue()/getDecimalValue()`
to avoid String allocation
#1305: Make helper methods of `WriterBasedJsonGenerator` non-final to allow overriding
(contributed by @zhangOranges)
#1310: Add new `StreamReadConstraints` (`maxTokenCount`) to limit maximum number
Expand Down
25 changes: 15 additions & 10 deletions src/main/java/com/fasterxml/jackson/core/base/ParserBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -847,6 +847,7 @@ public double getDoubleValue() throws IOException
if (_numTypesValid == NR_UNKNOWN) {
_parseNumericValue(NR_DOUBLE);
}
// if underlying type not FP, need conversion:
if ((_numTypesValid & NR_DOUBLE) == 0) {
convertNumberToDouble();
}
Expand Down Expand Up @@ -987,17 +988,19 @@ private void _parseSlowFloat(int expType) throws IOException
if (expType == NR_BIGDECIMAL) {
// 04-Dec-2022, tatu: Let's defer actual decoding until it is certain
// value is actually needed.
_numberBigDecimal = null;
_numberString = _textBuffer.contentsAsString();
// 24-Jun-2024, tatu: No; we shouldn't have to defer unless specifically
// request w/ `getNumberValueDeferred()` or so
_numberBigDecimal = _textBuffer.contentsAsDecimal(isEnabled(StreamReadFeature.USE_FAST_BIG_NUMBER_PARSER));
_numTypesValid = NR_BIGDECIMAL;
} else if (expType == NR_DOUBLE) {
_numberDouble = _textBuffer.contentsAsDouble(isEnabled(StreamReadFeature.USE_FAST_DOUBLE_PARSER));
_numTypesValid = NR_DOUBLE;
} else if (expType == NR_FLOAT) {
_numberFloat = 0.0f;
_numberString = _textBuffer.contentsAsString();
_numberFloat = _textBuffer.contentsAsFloat(isEnabled(StreamReadFeature.USE_FAST_DOUBLE_PARSER));
_numTypesValid = NR_FLOAT;
} else {
// Otherwise double has to do
// 04-Dec-2022, tatu: We can get all kinds of values here, NR_DOUBLE
// but also NR_INT or even NR_UNKNOWN. Shouldn't we try further
} else { // NR_UNKOWN, or one of int types
// 04-Dec-2022, tatu: We can get all kinds of values here
// (NR_INT, NR_LONG or even NR_UNKNOWN). Should we try further
// deferring some typing?
_numberDouble = 0.0;
_numberString = _textBuffer.contentsAsString();
Expand Down Expand Up @@ -1248,7 +1251,8 @@ protected BigInteger _convertBigDecimalToBigInteger(BigDecimal bigDec) throws IO
protected BigInteger _getBigInteger() throws JsonParseException {
if (_numberBigInt != null) {
return _numberBigInt;
} else if (_numberString == null) {
}
if (_numberString == null) {
throw new IllegalStateException("cannot get BigInteger from current parser state");
}
try {
Expand Down Expand Up @@ -1276,7 +1280,8 @@ protected BigInteger _getBigInteger() throws JsonParseException {
protected BigDecimal _getBigDecimal() throws JsonParseException {
if (_numberBigDecimal != null) {
return _numberBigDecimal;
} else if (_numberString == null) {
}
if (_numberString == null) {
throw new IllegalStateException("cannot get BigDecimal from current parser state");
}
try {
Expand Down
7 changes: 4 additions & 3 deletions src/main/java/com/fasterxml/jackson/core/io/NumberInput.java
Original file line number Diff line number Diff line change
Expand Up @@ -569,9 +569,10 @@ public static BigDecimal parseBigDecimal(final char[] ch) throws NumberFormatExc
* @since v2.15
*/
public static BigDecimal parseBigDecimal(final char[] ch, final boolean useFastParser) throws NumberFormatException {
return useFastParser ?
BigDecimalParser.parseWithFastParser(ch, 0, ch.length) :
BigDecimalParser.parse(ch);
if (useFastParser) {
return BigDecimalParser.parseWithFastParser(ch, 0, ch.length);
}
return BigDecimalParser.parse(ch);
}

/**
Expand Down
40 changes: 30 additions & 10 deletions src/main/java/com/fasterxml/jackson/core/util/TextBuffer.java
Original file line number Diff line number Diff line change
Expand Up @@ -551,7 +551,7 @@ public double contentsAsDouble(final boolean useFastParser) throws NumberFormatE
if (_inputStart >= 0) { // shared?
return NumberInput.parseDouble(_inputBuffer, _inputStart, _inputLen, useFastParser);
}
if (_currentSize == 0) { // all content in current segment!
if (!_hasSegments) { // all content in current segment!
return NumberInput.parseDouble(_currentSegment, 0, _currentSize, useFastParser);
}
if (_resultArray != null) {
Expand Down Expand Up @@ -626,7 +626,7 @@ public float contentsAsFloat(final boolean useFastParser) throws NumberFormatExc
if (_inputStart >= 0) { // shared?
return NumberInput.parseFloat(_inputBuffer, _inputStart, _inputLen, useFastParser);
}
if (_currentSize == 0) { // all content in current segment!
if (!_hasSegments) { // all content in current segment!
return NumberInput.parseFloat(_currentSegment, 0, _currentSize, useFastParser);
}
if (_resultArray != null) {
Expand All @@ -643,18 +643,38 @@ public float contentsAsFloat(final boolean useFastParser) throws NumberFormatExc
}

/**
* @return Buffered text value parsed as a {@link BigDecimal}, if possible
* @throws NumberFormatException if contents are not a valid Java number
*
* @deprecated Since 2.15 just access String contents if necessary, call
* {@link NumberInput#parseBigDecimal(String, boolean)} (or other overloads)
* directly instead
* @deprecated Since 2.15 use {@link #contentsAsDecimal(boolean)} instead.
*/
@Deprecated
public BigDecimal contentsAsDecimal() throws NumberFormatException {
// Was more optimized earlier, removing special handling due to deprecation
return contentsAsDecimal(false);
}

/**
* @since 2.18
*/
public BigDecimal contentsAsDecimal(final boolean useFastParser) throws NumberFormatException
{
// Order in which check is somewhat arbitrary... try likeliest ones
// that do not require allocation first

// except _resultString first since it works best with JDK (non-fast parser)
if (_resultString != null) {
return NumberInput.parseBigDecimal(_resultString, useFastParser);
}
if (_inputStart >= 0) { // shared?
return NumberInput.parseBigDecimal(_inputBuffer, _inputStart, _inputLen, useFastParser);
}
if (!_hasSegments) { // all content in current segment!
return NumberInput.parseBigDecimal(_currentSegment, 0, _currentSize, useFastParser);
}
if (_resultArray != null) {
return NumberInput.parseBigDecimal(_resultArray, useFastParser);
}

// Otherwise, segmented so need to use slow path
try {
return NumberInput.parseBigDecimal(contentsAsArray());
return NumberInput.parseBigDecimal(contentsAsArray(), useFastParser);
} catch (IOException e) {
// JsonParseException is used to denote a string that is too long
throw new NumberFormatException(e.getMessage());
Expand Down