Skip to content

Commit

Permalink
Fix #1284: avoid buffering-as-String for JsonParser.getFloat()/getDou…
Browse files Browse the repository at this point in the history
…ble()/getDecimal() (#1313)
  • Loading branch information
cowtowncoder authored Jun 25, 2024
1 parent 8bc5dba commit a5c3d99
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 23 deletions.
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

0 comments on commit a5c3d99

Please sign in to comment.