Skip to content

Commit cb808a3

Browse files
committed
Fix #1284: avoid buffering-as-String for JsonParser.getFloat()/getDouble()/getDecimal()
1 parent 8bc5dba commit cb808a3

File tree

4 files changed

+90
-18
lines changed

4 files changed

+90
-18
lines changed

release-notes/VERSION-2.x

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ a pure JSON library.
3333
#1274: `NUL`-corrupted keys, values on JSON serialization
3434
(reported, fix contributed by Jared S)
3535
#1277: Add back Java 22 optimisation in FastDoubleParser
36+
#1284: Optimize `JsonParser.getDoubleValue()/getFloatValue()/getDecimalValue()`
37+
to avoid String allocation
3638
#1305: Make helper methods of `WriterBasedJsonGenerator` non-final to allow overriding
3739
(contributed by @zhangOranges)
3840
#1310: Add new `StreamReadConstraints` (`maxTokenCount`) to limit maximum number

src/main/java/com/fasterxml/jackson/core/base/ParserBase.java

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -847,6 +847,7 @@ public double getDoubleValue() throws IOException
847847
if (_numTypesValid == NR_UNKNOWN) {
848848
_parseNumericValue(NR_DOUBLE);
849849
}
850+
// if underlying type not FP, need conversion:
850851
if ((_numTypesValid & NR_DOUBLE) == 0) {
851852
convertNumberToDouble();
852853
}
@@ -987,17 +988,19 @@ private void _parseSlowFloat(int expType) throws IOException
987988
if (expType == NR_BIGDECIMAL) {
988989
// 04-Dec-2022, tatu: Let's defer actual decoding until it is certain
989990
// value is actually needed.
990-
_numberBigDecimal = null;
991-
_numberString = _textBuffer.contentsAsString();
991+
// 24-Jun-2024, tatu: No; we shouldn't have to defer unless specifically
992+
// request w/ `getNumberValueDeferred()` or so
993+
_numberBigDecimal = _textBuffer.contentsAsDecimal(isEnabled(StreamReadFeature.USE_FAST_DOUBLE_PARSER));
992994
_numTypesValid = NR_BIGDECIMAL;
995+
} else if (expType == NR_DOUBLE) {
996+
_numberDouble = _textBuffer.contentsAsDouble(isEnabled(StreamReadFeature.USE_FAST_DOUBLE_PARSER));
997+
_numTypesValid = NR_DOUBLE;
993998
} else if (expType == NR_FLOAT) {
994-
_numberFloat = 0.0f;
995-
_numberString = _textBuffer.contentsAsString();
999+
_numberFloat = _textBuffer.contentsAsFloat(isEnabled(StreamReadFeature.USE_FAST_DOUBLE_PARSER));
9961000
_numTypesValid = NR_FLOAT;
997-
} else {
998-
// Otherwise double has to do
999-
// 04-Dec-2022, tatu: We can get all kinds of values here, NR_DOUBLE
1000-
// but also NR_INT or even NR_UNKNOWN. Shouldn't we try further
1001+
} else { // NR_UNKOWN, or one of int types
1002+
// 04-Dec-2022, tatu: We can get all kinds of values here
1003+
// (NR_INT, NR_LONG or even NR_UNKNOWN). Should we try further
10011004
// deferring some typing?
10021005
_numberDouble = 0.0;
10031006
_numberString = _textBuffer.contentsAsString();
@@ -1248,7 +1251,8 @@ protected BigInteger _convertBigDecimalToBigInteger(BigDecimal bigDec) throws IO
12481251
protected BigInteger _getBigInteger() throws JsonParseException {
12491252
if (_numberBigInt != null) {
12501253
return _numberBigInt;
1251-
} else if (_numberString == null) {
1254+
}
1255+
if (_numberString == null) {
12521256
throw new IllegalStateException("cannot get BigInteger from current parser state");
12531257
}
12541258
try {
@@ -1276,7 +1280,8 @@ protected BigInteger _getBigInteger() throws JsonParseException {
12761280
protected BigDecimal _getBigDecimal() throws JsonParseException {
12771281
if (_numberBigDecimal != null) {
12781282
return _numberBigDecimal;
1279-
} else if (_numberString == null) {
1283+
}
1284+
if (_numberString == null) {
12801285
throw new IllegalStateException("cannot get BigDecimal from current parser state");
12811286
}
12821287
try {

src/main/java/com/fasterxml/jackson/core/util/TextBuffer.java

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -643,18 +643,38 @@ public float contentsAsFloat(final boolean useFastParser) throws NumberFormatExc
643643
}
644644

645645
/**
646-
* @return Buffered text value parsed as a {@link BigDecimal}, if possible
647-
* @throws NumberFormatException if contents are not a valid Java number
648-
*
649-
* @deprecated Since 2.15 just access String contents if necessary, call
650-
* {@link NumberInput#parseBigDecimal(String, boolean)} (or other overloads)
651-
* directly instead
646+
* @deprecated Since 2.15 use {@link #contentsAsDecimal(boolean)} instead.
652647
*/
653648
@Deprecated
654649
public BigDecimal contentsAsDecimal() throws NumberFormatException {
655-
// Was more optimized earlier, removing special handling due to deprecation
650+
return contentsAsDecimal(false);
651+
}
652+
653+
/**
654+
* @since 2.18
655+
*/
656+
public BigDecimal contentsAsDecimal(final boolean useFastParser) throws NumberFormatException
657+
{
658+
// Order in which check is somewhat arbitrary... try likeliest ones
659+
// that do not require allocation first
660+
661+
// except _resultString first since it works best with JDK (non-fast parser)
662+
if (_resultString != null) {
663+
return NumberInput.parseBigDecimal(_resultString, useFastParser);
664+
}
665+
if (_inputStart >= 0) { // shared?
666+
return NumberInput.parseBigDecimal(_inputBuffer, _inputStart, _inputLen, useFastParser);
667+
}
668+
if (_currentSize == 0) { // all content in current segment!
669+
return NumberInput.parseBigDecimal(_currentSegment, 0, _currentSize, useFastParser);
670+
}
671+
if (_resultArray != null) {
672+
return NumberInput.parseBigDecimal(_resultArray, useFastParser);
673+
}
674+
675+
// Otherwise, segmented so need to use slow path
656676
try {
657-
return NumberInput.parseBigDecimal(contentsAsArray());
677+
return NumberInput.parseBigDecimal(contentsAsArray(), useFastParser);
658678
} catch (IOException e) {
659679
// JsonParseException is used to denote a string that is too long
660680
throw new NumberFormatException(e.getMessage());
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.fasterxml.jackson.core.base;
2+
3+
import com.fasterxml.jackson.core.*;
4+
import com.fasterxml.jackson.core.JsonParser.NumberType;
5+
6+
import static org.junit.jupiter.api.Assertions.assertEquals;
7+
import static org.junit.jupiter.api.Assertions.assertNull;
8+
9+
import org.junit.jupiter.api.Test;
10+
11+
// Tests to verify [core#1284]: no buffering for specific
12+
// access
13+
public class NumberReadDeferralTest extends JUnit5TestBase
14+
{
15+
private final JsonFactory JSON_F = newStreamFactory();
16+
17+
private final String NUM_DOC = "[ 0.1 ]";
18+
19+
@Test
20+
public void testDoubleWrtBuffering() throws Exception
21+
{
22+
try (ParserBase p = (ParserBase) JSON_F.createParser(NUM_DOC)) {
23+
assertNull(p._numberString);
24+
assertToken(JsonToken.START_ARRAY, p.nextToken());
25+
26+
assertToken(JsonToken.VALUE_NUMBER_FLOAT, p.nextToken());
27+
assertEquals(NumberType.DOUBLE, p.getNumberType());
28+
// assertNull(p._numberString);
29+
assertEquals(0.1, p.getDoubleValue());
30+
assertNull(p._numberString);
31+
}
32+
}
33+
34+
@Test
35+
public void testFloatWrtBuffering() throws Exception
36+
{
37+
38+
}
39+
40+
@Test
41+
public void testBigDecimalWrtBuffering() throws Exception
42+
{
43+
44+
}
45+
}

0 commit comments

Comments
 (0)