Summary
JsonNumber.of(double) passes hardcoded (decimalOffset=0, exponentOffset=0) to JsonNumberImpl, which causes toLong() to fail even for whole numbers like 1.0, 1000.0, or 1e10.
This bug exists in upstream OpenJDK jdk-sandbox json branch as well.
Steps to Reproduce
On the sync-upstream-api branch, run the demo class:
git checkout sync-upstream-api
mvn -pl json-java21 clean compile test-compile
java -cp json-java21/target/classes:json-java21/target/test-classes \
jdk.sandbox.internal.util.json.PrReviewIssuesDemo
Expected Behavior
JsonNumber.of(1.0).toLong() // Should return 1L
JsonNumber.of(1000.0).toLong() // Should return 1000L
JsonNumber.of(1e10).toLong() // Should return 10000000000L
Actual Behavior
Test: JsonNumber.of(1.0)
toString() = '1.0'
toLong() threw: 1.0 cannot be represented as a long. Path: "". Location: line 0, position 0.
[CONFIRMED BUG]
Test: JsonNumber.of(1000.0)
toString() = '1000.0'
toLong() threw: 1000.0 cannot be represented as a long. Path: "". Location: line 0, position 0.
[CONFIRMED BUG]
Test: JsonNumber.of(1e10) - produces '1.0E10'
toString() = '1.0E10'
toLong() threw: 1.0E10 cannot be represented as a long. Path: "". Location: line 0, position 0.
[CONFIRMED BUG]
However, parsing the same value from a JSON string works correctly:
Control: Json.parse("1.0") - parsed from string
toString() = '1.0'
toLong() = 1 [Parser sets correct offsets]
Root Cause
In JsonNumber.java line 79-80:
static JsonNumber of(double num) {
if (!Double.isFinite(num)) {
throw new IllegalArgumentException("Not a valid JSON number");
}
var str = Double.toString(num);
return new JsonNumberImpl(str.toCharArray(), 0, str.length(), 0, 0);
// ^ ^
// decimalOffset exponentOffset
}
The problem is decimalOffset=0 and exponentOffset=0 are passed regardless of where (or if) the decimal point and exponent actually appear in the string.
For "1.0":
- Actual decimal position is at index 1
- Passed
decimalOffset=0 (wrong)
The initNumLong() method in JsonNumberImpl uses these offsets to determine how to parse the number. When decimalOffset != -1, it takes a branch that expects the decimal at that position, leading to incorrect parsing.
Upstream Reference
This bug exists in upstream OpenJDK:
Potential Fix
Compute the actual decimal and exponent offsets from the string:
static JsonNumber of(double num) {
if (!Double.isFinite(num)) {
throw new IllegalArgumentException("Not a valid JSON number");
}
var str = Double.toString(num);
var chars = str.toCharArray();
int decOff = str.indexOf('.');
int expOff = Math.max(str.indexOf('e'), str.indexOf('E'));
return new JsonNumberImpl(chars, 0, chars.length, decOff, expOff);
}
Decision Required
Since this is a backport, we have options:
- Keep as-is - faithfully reproduce upstream bug, document the limitation
- Fix locally - diverge from upstream to fix the bug (may complicate future syncs)
- Report upstream - file a bug with OpenJDK and wait for their fix
Related
Summary
JsonNumber.of(double)passes hardcoded(decimalOffset=0, exponentOffset=0)toJsonNumberImpl, which causestoLong()to fail even for whole numbers like1.0,1000.0, or1e10.This bug exists in upstream OpenJDK
jdk-sandboxjson branch as well.Steps to Reproduce
On the
sync-upstream-apibranch, run the demo class:Expected Behavior
Actual Behavior
However, parsing the same value from a JSON string works correctly:
Root Cause
In
JsonNumber.javaline 79-80:The problem is
decimalOffset=0andexponentOffset=0are passed regardless of where (or if) the decimal point and exponent actually appear in the string.For
"1.0":decimalOffset=0(wrong)The
initNumLong()method inJsonNumberImpluses these offsets to determine how to parse the number. WhendecimalOffset != -1, it takes a branch that expects the decimal at that position, leading to incorrect parsing.Upstream Reference
This bug exists in upstream OpenJDK:
Potential Fix
Compute the actual decimal and exponent offsets from the string:
Decision Required
Since this is a backport, we have options:
Related