JsonPath: large negative JSON numbers overflow to -Infinity (sign-blind float/double selection)
Description
Under the default JsonPath configuration (NumberReturnType.FLOAT_AND_DOUBLE, no config needed), JsonPath.get(path) chooses between float and double for a non-integer number with a sign-blind upper-bound test. A positive value above Float.MAX_VALUE (~3.4e38) stays a double. A negative value of the same magnitude falls into the float branch, where BigDecimal.floatValue() overflows to Float.NEGATIVE_INFINITY. The valid finite JSON number -1e40 comes back as -Infinity, with no exception and no warning.
JSON: {"pos": 1e40, "neg": -1e40}
get("pos") = 1e40 -> java.lang.Double = 1.0E40 (correct)
get("neg") = -1e40 -> java.lang.Float = -Infinity (finite value destroyed)Same magnitude, opposite sign, divergent outcome. rest-assured's own XmlPath.getDouble returns the correct -1.0E40 for the identical value.
rest-assured version
- Reproduced on released
io.rest-assured:json-path:5.5.6(Maven Central) and onmasterHEADeeed4998(6.0.2-SNAPSHOT). Present unchanged from the Groovy 5.x form through the migrated Java form. - Java 24 (Temurin 24.0.1). Default JsonPath config.
Steps to reproduce
Minimal, self-contained (json-path + Groovy on the classpath):
import io.restassured.path.json.JsonPath;
public class Repro {
public static void main(String[] a) {
JsonPath jp = JsonPath.from("{\"pos\": 1e40, \"neg\": -1e40}");
System.out.println("get(pos) = " + jp.get("pos") + " (" + jp.get("pos").getClass().getSimpleName() + ")");
System.out.println("get(neg) = " + jp.get("neg") + " (" + jp.get("neg").getClass().getSimpleName() + ")");
System.out.println("getDouble(neg) = " + jp.getDouble("neg"));
}
}Output:
get(pos) = 1.0E40 (Double)
get(neg) = -Infinity (Float) <-- BUG
getDouble(neg) = -Infinity <-- inherits the corruptionA boundary sweep pins the asymmetry at Float.MAX_VALUE. Both signs agree while |x| <= Float.MAX_VALUE. The divergence starts the instant |x| > Float.MAX_VALUE, and only on the negative side: -3.5e38, -4.0e38, -1e40 all return -Infinity while their positive twins return Double values.
Expected
get("neg") / getDouble("neg") return the finite -1.0E40 (as a Double), symmetric with the positive case and matching com.jayway.jsonpath 2.9.0, raw Double.parseDouble, and rest-assured's own XmlPath.getDouble.
Actual
-Infinity. A valid finite number overflows to negative infinity with no error.
Root cause
json-path/src/main/java/io/restassured/internal/path/json/ConfigurableJsonSlurper.java (HEAD eeed4998), inside the BigDecimal to float/double conversion:
if (numberReturnType == NumberReturnType.DOUBLE
|| decimal.compareTo(BigDecimal.valueOf(Float.MAX_VALUE)) > 0) {
result = decimal.doubleValue();
} else {
result = decimal.floatValue(); // overflows to +/-Infinity for |x| > Float.MAX_VALUE
}The guard tests the positive upper bound alone: decimal.compareTo(BigDecimal.valueOf(Float.MAX_VALUE)) > 0. Every negative decimal compares below Float.MAX_VALUE, so the test fails and control reaches the float branch, where BigDecimal.floatValue() returns Float.NEGATIVE_INFINITY for out-of-range magnitudes. The released 5.5.6 Groovy form (ConfigurableJsonSlurper.groovy, if (result > Float.MAX_VALUE || ...)) has the same defect. Only the default FLOAT_AND_DOUBLE path is affected; DOUBLE and BIG_DECIMAL behave.
Suggested fix
Compare magnitude instead of the signed value:
if (numberReturnType == NumberReturnType.DOUBLE
|| decimal.abs().compareTo(BigDecimal.valueOf(Float.MAX_VALUE)) > 0) {
result = decimal.doubleValue();
} else {
result = decimal.floatValue();
}(Groovy 5.x form: if (result.abs() > Float.MAX_VALUE || numberReturnType == NumberReturnType.DOUBLE).)
Found via property-based & differential bug-hunting, part of an effort to scale PBT (DepTyCheck-based) testing across the OSS ecosystem.
If this is intended / by-design: I'm really sorry, please just close it — no need to flag or ban me. I'm trying to scale property-based testing across the whole ecosystem and my publishing agents may have gotten this one wrong. I read every issue and follow up on each.
Source: rest-assured/rest-assured