#1076·JsonPath

Array slice [from:to] with a negative from wraps around and returns duplicate elements

Author: haskiindahouseCreated Jul 21, 2026Updated Jul 21, 2026

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+). Default JSON provider.

Description

A two-bound slice [from:to] never normalizes a negative from. The per-index handler downstream does normalize negatives, so the slice wraps to the end of the array and then continues from the start, returning out-of-order and duplicated elements. Single-bound slices ([from:] and [:to]) normalize correctly; only the two-bound form breaks.

Steps to reproduce

java
import com.jayway.jsonpath.*;

String json = "[0,1,2,3,4]";

net.minidev.json.JSONArray r = JsonPath.read(json, "$[-2:4]");
System.out.println(r);

Observed output

$[-2:4]  => [3,4,0,1,2,3]
$[-2:]   => [3,4]        (single-bound, correct)
$[:4]    => [0,1,2,3]    (single-bound, correct)
$[-3:2]  => [2,3,4,0,1]  (two-bound, also wraps)

Expected

[3]. Standard slice semantics select index 3 only for [-2:4].

Actual

[3,4,0,1,2,3]. The negative from wraps to the end (3,4), then the loop continues 0,1,2,3, duplicating 3.

Root cause

json-path/src/main/java/com/jayway/jsonpath/internal/path/ArraySliceToken.java, sliceBetween (line 67):

java
67:  private void sliceBetween(...) {
68:      int length = ctx.jsonProvider().length(model);
69:      int from = operation.from();
70:      int to = operation.to();
71:
72:      to = Math.min(length, to);        // only `to` is handled
73:
74:      if (from >= to || length == 0) { return; }
...
80:      for (int i = from; i < to; i++) {         // i starts negative
81:          handleArrayIndex(i, currentPath, model, ctx);  // maps i<0 to length+i
82:      }
83:  }

from is never normalized or clamped. sliceFrom (lines 48–55) does from = length + from; from = Math.max(0, from);, but sliceBetween skips that. With a negative from, the loop counter i starts negative and handleArrayIndex (PathToken.java:137) maps each negative i to length + i, emitting the tail elements before the loop reaches index 0.

Suggested fix

Normalize and clamp from before the guard, mirroring sliceFrom:

java
if (from < 0) {
    from = length + from;
}
from = Math.max(0, from);

placed before the from >= to check.


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.