Version / environment
- json-path
master @ HEAD (line numbers verified against source); latest release json-path 3.0.0 hits the same code path.
- JVM: any (Java 8+). JSON provider: default (json-smart); reproduced cross-provider.
Description
JsonPath.read(json, "$[-1]") returns the last element. The update operations set, delete, and map with the same $[-1] throw a raw java.lang.IndexOutOfBoundsException that escapes the library. Read and write disagree on what $[-1] means, and callers get a JDK exception instead of the library's own PathNotFoundException.
Steps to reproduce
import com.jayway.jsonpath.*;
String json = "[\"a\",\"b\",\"c\"]";
// READ with negative index, returns "c"
Object last = JsonPath.read(json, "$[-1]");
System.out.println(last); // c
// SET with the same negative index, throws
DocumentContext ctx = JsonPath.parse(json);
ctx.set("$[-1]", "z"); // java.lang.IndexOutOfBoundsException: Index -1 out of bounds for length 3
// delete("$[-1]") and map("$[-1]", ...) throw the same way
Observed output
READ $[-1]: c
SET threw: java.lang.IndexOutOfBoundsException: Index -1 out of bounds for length 3
DELETE threw: java.lang.IndexOutOfBoundsException: Index -1 out of bounds for length 3
MAP threw: java.lang.IndexOutOfBoundsException: Index -1 out of bounds for length 3
Expected
set/delete/map on $[-1] update the last element, matching how read resolves $[-1]. Failing that, they raise PathNotFoundException, the library's own type, instead of leaking a raw IndexOutOfBoundsException from the provider.
Root cause
json-path/src/main/java/com/jayway/jsonpath/internal/path/PathToken.java, handleArrayIndex (line 136):
136: PathRef pathRef = ctx.forUpdate() ? PathRef.create(model, index) : PathRef.NO_OP;
137: int effectiveIndex = index < 0 ? ctx.jsonProvider().length(model) + index : index;
138: try {
139: Object evalHit = ctx.jsonProvider().getArrayIndex(model, effectiveIndex);
...
145: } catch (IndexOutOfBoundsException e) { // guards the READ only
146: }
Line 136 builds the update PathRef from the raw index. Line 137 normalizes the negative index into effectiveIndex for the read at line 139, and the read sits inside the IOOBE catch at line 145. The update runs later with the raw negative index, reaches the provider, and throws outside any guard.
Suggested fix
Normalize the negative index before building the update PathRef, so update and read agree:
int effectiveIndex = index < 0 ? ctx.jsonProvider().length(model) + index : index;
PathRef pathRef = ctx.forUpdate() ? PathRef.create(model, effectiveIndex) : PathRef.NO_OP;
Convert an out-of-range update into PathNotFoundException rather than letting IndexOutOfBoundsException escape.
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.
Version / environment
master@ HEAD (line numbers verified against source); latest release json-path 3.0.0 hits the same code path.Description
JsonPath.read(json, "$[-1]")returns the last element. The update operationsset,delete, andmapwith the same$[-1]throw a rawjava.lang.IndexOutOfBoundsExceptionthat escapes the library. Read and write disagree on what$[-1]means, and callers get a JDK exception instead of the library's ownPathNotFoundException.Steps to reproduce
Observed output
Expected
set/delete/mapon$[-1]update the last element, matching how read resolves$[-1]. Failing that, they raisePathNotFoundException, the library's own type, instead of leaking a rawIndexOutOfBoundsExceptionfrom the provider.Root cause
json-path/src/main/java/com/jayway/jsonpath/internal/path/PathToken.java,handleArrayIndex(line 136):Line 136 builds the update
PathReffrom the rawindex. Line 137 normalizes the negative index intoeffectiveIndexfor the read at line 139, and the read sits inside the IOOBE catch at line 145. The update runs later with the raw negative index, reaches the provider, and throws outside any guard.Suggested fix
Normalize the negative index before building the update
PathRef, so update and read agree:Convert an out-of-range update into
PathNotFoundExceptionrather than lettingIndexOutOfBoundsExceptionescape.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.