Skip to content

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

Description

@haskiindahouse

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

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):

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:

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions