diff --git a/LICENSE b/LICENSE
index d6456956..9972f34b 100644
--- a/LICENSE
+++ b/LICENSE
@@ -187,7 +187,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
- Copyright [yyyy] [name of copyright owner]
+ Copyright 2017 Jayway
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/README.md b/README.md
index 5a7a6c85..e0953842 100644
--- a/README.md
+++ b/README.md
@@ -155,6 +155,7 @@ Given the json
| $.store.* | All things, both books and bicycles |
| $.store..price | The price of everything |
| $..book[2] | The third book |
+| $..book[-2] | The second to last book |
| $..book[0,1] | The first two books |
| $..book[:2] | All books from index 0 (inclusive) until index 2 (exclusive) |
| $..book[1:2] | All books from index 1 (inclusive) until index 2 (exclusive) |
diff --git a/build.gradle b/build.gradle
index fffe227a..fec4e271 100644
--- a/build.gradle
+++ b/build.gradle
@@ -12,7 +12,7 @@ buildscript {
ext {
libs = [
slf4jApi: 'org.slf4j:slf4j-api:1.7.16',
- jsonSmart: 'net.minidev:json-smart:2.2.1',
+ jsonSmart: 'net.minidev:json-smart:2.3',
jacksonDatabind: 'com.fasterxml.jackson.core:jackson-databind:2.6.3',
gson: 'com.google.code.gson:gson:2.3.1',
jettison: 'org.codehaus.jettison:jettison:1.3.7',
diff --git a/json-path/build.gradle b/json-path/build.gradle
index c86e35f0..812eac64 100644
--- a/json-path/build.gradle
+++ b/json-path/build.gradle
@@ -11,10 +11,7 @@ jar {
}
dependencies {
- compile (libs.jsonSmart){
- // see https://github.com/jayway/JsonPath/issues/228, https://github.com/netplex/json-smart-v2/issues/20
- exclude group: 'org.ow2.asm', module: 'asm'
- }
+ compile libs.jsonSmart
compile libs.slf4jApi
compile libs.jacksonDatabind, optional
compile libs.gson, optional
diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/CharacterIndex.java b/json-path/src/main/java/com/jayway/jsonpath/internal/CharacterIndex.java
index b93811c3..1aa28679 100644
--- a/json-path/src/main/java/com/jayway/jsonpath/internal/CharacterIndex.java
+++ b/json-path/src/main/java/com/jayway/jsonpath/internal/CharacterIndex.java
@@ -17,14 +17,16 @@ public class CharacterIndex {
private final CharSequence charSequence;
private int position;
+ private int endPosition;
public CharacterIndex(CharSequence charSequence) {
this.charSequence = charSequence;
this.position = 0;
+ this.endPosition = charSequence.length() - 1;
}
public int length() {
- return charSequence.length();
+ return endPosition + 1;
}
public char charAt(int idx) {
@@ -39,6 +41,10 @@ public class CharacterIndex {
return (charSequence.charAt(position) == c);
}
+ public boolean lastCharIs(char c) {
+ return charSequence.charAt(endPosition) == c;
+ }
+
public boolean nextCharIs(char c) {
return inBounds(position + 1) && (charSequence.charAt(position + 1) == c);
}
@@ -47,12 +53,21 @@ public class CharacterIndex {
return setPosition(position + charCount);
}
+ public int decrementEndPosition(int charCount) {
+ return setEndPosition(endPosition - charCount);
+ }
+
public int setPosition(int newPosition) {
//position = min(newPosition, charSequence.length() - 1);
position = newPosition;
return position;
}
+ private int setEndPosition(int newPosition) {
+ endPosition = newPosition;
+ return endPosition;
+ }
+
public int position(){
return position;
}
@@ -244,7 +259,7 @@ public class CharacterIndex {
}
public boolean currentIsTail() {
- return position >= charSequence.length()-1;
+ return position >= endPosition;
}
public boolean hasMoreCharacters() {
@@ -252,7 +267,7 @@ public class CharacterIndex {
}
public boolean inBounds(int idx) {
- return (idx >= 0) && (idx < charSequence.length());
+ return (idx >= 0) && (idx <= endPosition);
}
public boolean inBounds() {
return inBounds(position);
@@ -281,9 +296,22 @@ public class CharacterIndex {
}
public CharacterIndex skipBlanks() {
- while (inBounds() && currentChar() == SPACE){
+ while (inBounds() && position < endPosition && currentChar() == SPACE){
incrementPosition(1);
}
return this;
}
+
+ private CharacterIndex skipBlanksAtEnd() {
+ while (inBounds() && position < endPosition && lastCharIs(SPACE)){
+ decrementEndPosition(1);
+ }
+ return this;
+ }
+
+ public CharacterIndex trim() {
+ skipBlanks();
+ skipBlanksAtEnd();
+ return this;
+ }
}
\ No newline at end of file
diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/FilterCompiler.java b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/FilterCompiler.java
index e5eb39a3..67b512e4 100644
--- a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/FilterCompiler.java
+++ b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/FilterCompiler.java
@@ -54,20 +54,22 @@ public class FilterCompiler {
}
private FilterCompiler(String filterString) {
- filterString = filterString.trim();
- if (!filterString.startsWith("[") || !filterString.endsWith("]")) {
+ filter = new CharacterIndex(filterString);
+ filter.trim();
+ if (!filter.currentCharIs('[') || !filter.lastCharIs(']')) {
throw new InvalidPathException("Filter must start with '[' and end with ']'. " + filterString);
}
- filterString = filterString.substring(1, filterString.length() - 1).trim();
- if (!filterString.startsWith("?")) {
+ filter.incrementPosition(1);
+ filter.decrementEndPosition(1);
+ filter.trim();
+ if (!filter.currentCharIs('?')) {
throw new InvalidPathException("Filter must start with '[?' and end with ']'. " + filterString);
}
- filterString = filterString.substring(1).trim();
- if (!filterString.startsWith("(") || !filterString.endsWith(")")) {
+ filter.incrementPosition(1);
+ filter.trim();
+ if (!filter.currentCharIs('(') || !filter.lastCharIs(')')) {
throw new InvalidPathException("Filter must start with '[?(' and end with ')]'. " + filterString);
}
-
- filter = new CharacterIndex(filterString);
}
public Predicate compile() {
diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ValueNode.java b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ValueNode.java
index 6a8a4a8e..08d257c4 100644
--- a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ValueNode.java
+++ b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ValueNode.java
@@ -158,7 +158,7 @@ public abstract class ValueNode {
if ((c0 == '[' && c1 == ']') || (c0 == '{' && c1 == '}')){
try {
new JSONParser(JSONParser.MODE_PERMISSIVE).parse(str);
- return false;
+ return true;
} catch(Exception e){
return false;
}
diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/path/ArrayIndexOperation.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/ArrayIndexOperation.java
index 8fde93a9..cbfa0253 100644
--- a/json-path/src/main/java/com/jayway/jsonpath/internal/path/ArrayIndexOperation.java
+++ b/json-path/src/main/java/com/jayway/jsonpath/internal/path/ArrayIndexOperation.java
@@ -42,7 +42,7 @@ public class ArrayIndexOperation {
//check valid chars
for (int i = 0; i < operation.length(); i++) {
char c = operation.charAt(i);
- if (!isDigit(c) && c != ',' && c != ' ') {
+ if (!isDigit(c) && c != ',' && c != ' ' && c != '-') {
throw new InvalidPathException("Failed to parse ArrayIndexOperation: " + operation);
}
}
diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathCompiler.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathCompiler.java
index f83e953a..36d6b822 100644
--- a/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathCompiler.java
+++ b/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathCompiler.java
@@ -45,9 +45,13 @@ public class PathCompiler {
private final LinkedList filterStack;
private final CharacterIndex path;
- private PathCompiler(String path, LinkedList filterStack) {
+ private PathCompiler(String path, LinkedList filterStack){
+ this(new CharacterIndex(path), filterStack);
+ }
+
+ private PathCompiler(CharacterIndex path, LinkedList filterStack){
this.filterStack = filterStack;
- this.path = new CharacterIndex(path);
+ this.path = path;
}
private Path compile() {
@@ -57,16 +61,18 @@ public class PathCompiler {
public static Path compile(String path, final Predicate... filters) {
try {
- path = path.trim();
+ CharacterIndex ci = new CharacterIndex(path);
+ ci.trim();
- if(!(path.charAt(0) == DOC_CONTEXT) && !(path.charAt(0) == EVAL_CONTEXT)){
- path = "$." + path;
+ if(!( ci.charAt(0) == DOC_CONTEXT) && !( ci.charAt(0) == EVAL_CONTEXT)){
+ ci = new CharacterIndex("$." + path);
+ ci.trim();
}
- if(path.endsWith(".")){
+ if(ci.lastCharIs('.')){
fail("Path must not end with a '.' or '..'");
}
- LinkedList filterStack = new LinkedList(asList(filters));
- Path p = new PathCompiler(path.trim(), filterStack).compile();
+ LinkedList filterStack = new LinkedList(asList(filters));
+ Path p = new PathCompiler(ci, filterStack).compile();
return p;
} catch (Exception e) {
InvalidPathException ipe;
@@ -103,7 +109,6 @@ public class PathCompiler {
}
RootPathToken pathToken = PathTokenFactory.createRootPathToken(path.currentChar());
- PathTokenAppender appender = pathToken.getPathTokenAppender();
if (path.currentIsTail()) {
return pathToken;
@@ -115,6 +120,7 @@ public class PathCompiler {
fail("Illegal character at position " + path.position() + " expected '.' or '[");
}
+ PathTokenAppender appender = pathToken.getPathTokenAppender();
readNextToken(appender);
return pathToken;
@@ -261,7 +267,6 @@ public class PathCompiler {
* an array.
*/
private List parseFunctionParameters(String funcName) {
- PathToken currentToken;
ParamType type = null;
// Parenthesis starts at 1 since we're marking the start of a function call, the close paren will denote the
diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathToken.java b/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathToken.java
index dc22dda1..e4d23604 100644
--- a/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathToken.java
+++ b/json-path/src/main/java/com/jayway/jsonpath/internal/path/PathToken.java
@@ -125,8 +125,9 @@ public abstract class PathToken {
protected void handleArrayIndex(int index, String currentPath, Object model, EvaluationContextImpl ctx) {
String evalPath = Utils.concat(currentPath, "[", String.valueOf(index), "]");
PathRef pathRef = ctx.forUpdate() ? PathRef.create(model, index) : PathRef.NO_OP;
+ int effectiveIndex = index < 0 ? ctx.jsonProvider().length(model) + index : index;
try {
- Object evalHit = ctx.jsonProvider().getArrayIndex(model, index);
+ Object evalHit = ctx.jsonProvider().getArrayIndex(model, effectiveIndex);
if (isLeaf()) {
ctx.addResult(evalPath, pathRef, evalHit);
} else {
diff --git a/json-path/src/main/java/com/jayway/jsonpath/spi/json/JsonOrgJsonProvider.java b/json-path/src/main/java/com/jayway/jsonpath/spi/json/JsonOrgJsonProvider.java
index 6760f869..0c5f169e 100644
--- a/json-path/src/main/java/com/jayway/jsonpath/spi/json/JsonOrgJsonProvider.java
+++ b/json-path/src/main/java/com/jayway/jsonpath/spi/json/JsonOrgJsonProvider.java
@@ -96,8 +96,8 @@ public class JsonOrgJsonProvider extends AbstractJsonProvider {
public Object getMapValue(Object obj, String key) {
try {
JSONObject jsonObject = toJsonObject(obj);
- Object o = jsonObject.get(key);
- if (!jsonObject.has(key)) {
+ Object o = jsonObject.opt(key);
+ if (o == null) {
return UNDEFINED;
} else {
return unwrap(o);
diff --git a/json-path/src/test/java/com/jayway/jsonpath/FilterTest.java b/json-path/src/test/java/com/jayway/jsonpath/FilterTest.java
index 58493a35..a9c02e76 100644
--- a/json-path/src/test/java/com/jayway/jsonpath/FilterTest.java
+++ b/json-path/src/test/java/com/jayway/jsonpath/FilterTest.java
@@ -89,6 +89,13 @@ public class FilterTest extends BaseTest {
assertThat(filter(where("string-key").eq(null)).apply(createPredicateContext(json))).isEqualTo(false);
}
+ @Test
+ public void arr_eq_evals() {
+ assertThat(filter(where("arr-empty").eq("[]")).apply(createPredicateContext(json))).isEqualTo(true);
+ assertThat(filter(where("int-arr").eq("[0,1,2,3,4]")).apply(createPredicateContext(json))).isEqualTo(true);
+ assertThat(filter(where("int-arr").eq("[0,1,2,3]")).apply(createPredicateContext(json))).isEqualTo(false);
+ assertThat(filter(where("int-arr").eq("[0,1,2,3,4,5]")).apply(createPredicateContext(json))).isEqualTo(false);
+ }
//----------------------------------------------------------------------------
//
// NE
diff --git a/json-path/src/test/java/com/jayway/jsonpath/JsonOrgJsonProviderTest.java b/json-path/src/test/java/com/jayway/jsonpath/JsonOrgJsonProviderTest.java
index d24e30d1..59ba665c 100644
--- a/json-path/src/test/java/com/jayway/jsonpath/JsonOrgJsonProviderTest.java
+++ b/json-path/src/test/java/com/jayway/jsonpath/JsonOrgJsonProviderTest.java
@@ -44,4 +44,12 @@ public class JsonOrgJsonProviderTest extends BaseTest {
assertThat(books.size()).isEqualTo(4);
}
+
+ @Test
+ public void read_books_with_isbn() {
+
+ JSONArray books = using(JSON_ORG_CONFIGURATION).parse(JSON_DOCUMENT).read("$..book[?(@.isbn)]");
+
+ assertThat(books.length()).isEqualTo(2);
+ }
}
diff --git a/json-path/src/test/java/com/jayway/jsonpath/ProviderInTest.java b/json-path/src/test/java/com/jayway/jsonpath/ProviderInTest.java
new file mode 100644
index 00000000..0508539c
--- /dev/null
+++ b/json-path/src/test/java/com/jayway/jsonpath/ProviderInTest.java
@@ -0,0 +1,128 @@
+package com.jayway.jsonpath;
+
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.google.gson.JsonArray;
+import com.jayway.jsonpath.spi.json.GsonJsonProvider;
+import com.jayway.jsonpath.spi.json.JacksonJsonNodeJsonProvider;
+import com.jayway.jsonpath.spi.json.JacksonJsonProvider;
+import com.jayway.jsonpath.spi.json.JsonOrgJsonProvider;
+import com.jayway.jsonpath.spi.json.JsonSmartJsonProvider;
+import com.jayway.jsonpath.spi.mapper.GsonMappingProvider;
+import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider;
+import com.jayway.jsonpath.spi.mapper.JsonOrgMappingProvider;
+import com.jayway.jsonpath.spi.mapper.JsonSmartMappingProvider;
+import org.assertj.core.util.Lists;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+
+public class ProviderInTest {
+ private final String JSON = "[{\"foo\": \"bar\"}, {\"foo\": \"baz\"}]";
+ private final String EQUALS_FILTER = "$.[?(@.foo == %s)].foo";
+ private final String IN_FILTER = "$.[?(@.foo in [%s])].foo";
+ private final String DOUBLE_QUOTES = "\"bar\"";
+ private final String DOUBLE_QUOTES_EQUALS_FILTER = String.format(EQUALS_FILTER, DOUBLE_QUOTES);
+ private final String DOUBLE_QUOTES_IN_FILTER = String.format(IN_FILTER, DOUBLE_QUOTES);
+ private final String SINGLE_QUOTES = "'bar'";
+ private final String SINGLE_QUOTES_EQUALS_FILTER = String.format(EQUALS_FILTER, SINGLE_QUOTES);
+ private final String SINGLE_QUOTES_IN_FILTER = String.format(IN_FILTER, SINGLE_QUOTES);
+
+
+ @Rule
+ public final ExpectedException exception = ExpectedException.none();
+
+ @Test
+ public void testJsonPathQuotesJackson() throws Exception {
+ final Configuration jackson = Configuration.builder().jsonProvider(new JacksonJsonProvider()).mappingProvider(new JacksonMappingProvider()).build();
+ final DocumentContext ctx = JsonPath.using(jackson).parse(JSON);
+
+ final List doubleQuoteEqualsResult = ctx.read(DOUBLE_QUOTES_EQUALS_FILTER);
+ assertEquals(Lists.newArrayList("bar"), doubleQuoteEqualsResult);
+
+ final List singleQuoteEqualsResult = ctx.read(SINGLE_QUOTES_EQUALS_FILTER);
+ assertEquals(doubleQuoteEqualsResult, singleQuoteEqualsResult);
+
+ final List doubleQuoteInResult = ctx.read(DOUBLE_QUOTES_IN_FILTER);
+ assertEquals(doubleQuoteInResult, doubleQuoteEqualsResult);
+
+ exception.expect(InvalidJsonException.class);
+ ctx.read(SINGLE_QUOTES_IN_FILTER);
+ }
+
+
+ @Test
+ public void testJsonPathQuotesJacksonJsonNode() throws Exception {
+ final Configuration jacksonJsonNode = Configuration.builder().jsonProvider(new JacksonJsonNodeJsonProvider()).mappingProvider(new JacksonMappingProvider()).build();
+ final DocumentContext ctx = JsonPath.using(jacksonJsonNode).parse(JSON);
+
+ final ArrayNode doubleQuoteEqualsResult = ctx.read(DOUBLE_QUOTES_EQUALS_FILTER);
+ assertEquals("bar", doubleQuoteEqualsResult.get(0).asText());
+
+ final ArrayNode singleQuoteEqualsResult = ctx.read(SINGLE_QUOTES_EQUALS_FILTER);
+ assertEquals(doubleQuoteEqualsResult, singleQuoteEqualsResult);
+
+ final ArrayNode doubleQuoteInResult = ctx.read(DOUBLE_QUOTES_IN_FILTER);
+ assertEquals(doubleQuoteInResult, doubleQuoteEqualsResult);
+
+ exception.expect(InvalidJsonException.class);
+ ctx.read(SINGLE_QUOTES_IN_FILTER);
+ }
+
+ @Test
+ public void testJsonPathQuotesGson() throws Exception {
+ final Configuration gson = Configuration.builder().jsonProvider(new GsonJsonProvider()).mappingProvider(new GsonMappingProvider()).build();
+ final DocumentContext ctx = JsonPath.using(gson).parse(JSON);
+
+ final JsonArray doubleQuoteEqualsResult = ctx.read(DOUBLE_QUOTES_EQUALS_FILTER);
+ assertEquals("bar", doubleQuoteEqualsResult.get(0).getAsString());
+
+ final JsonArray singleQuoteEqualsResult = ctx.read(SINGLE_QUOTES_EQUALS_FILTER);
+ assertEquals(doubleQuoteEqualsResult, singleQuoteEqualsResult);
+
+ final JsonArray doubleQuoteInResult = ctx.read(DOUBLE_QUOTES_IN_FILTER);
+ assertEquals(doubleQuoteInResult, doubleQuoteEqualsResult);
+
+ final JsonArray singleQuoteInResult = ctx.read(SINGLE_QUOTES_IN_FILTER);
+ assertEquals(doubleQuoteInResult, singleQuoteInResult);
+ }
+
+ @Test
+ public void testJsonPathQuotesJsonOrg() throws Exception {
+ final Configuration jsonOrg = Configuration.builder().jsonProvider(new JsonOrgJsonProvider()).mappingProvider(new JsonOrgMappingProvider()).build();
+ final DocumentContext ctx = JsonPath.using(jsonOrg).parse(JSON);
+
+ final org.json.JSONArray doubleQuoteEqualsResult = ctx.read(DOUBLE_QUOTES_EQUALS_FILTER);
+ assertEquals("bar", doubleQuoteEqualsResult.get(0));
+
+ final org.json.JSONArray singleQuoteEqualsResult = ctx.read(SINGLE_QUOTES_EQUALS_FILTER);
+ assertEquals(doubleQuoteEqualsResult.get(0), singleQuoteEqualsResult.get(0));
+
+ final org.json.JSONArray doubleQuoteInResult = ctx.read(DOUBLE_QUOTES_IN_FILTER);
+ assertEquals(doubleQuoteInResult.get(0), doubleQuoteEqualsResult.get(0));
+
+ final org.json.JSONArray singleQuoteInResult = ctx.read(SINGLE_QUOTES_IN_FILTER);
+ assertEquals(doubleQuoteInResult.get(0), singleQuoteInResult.get(0));
+ }
+
+ @Test
+ public void testJsonPathQuotesJsonSmart() throws Exception {
+ final Configuration jsonSmart = Configuration.builder().jsonProvider(new JsonSmartJsonProvider()).mappingProvider(new JsonSmartMappingProvider()).build();
+ final DocumentContext ctx = JsonPath.using(jsonSmart).parse(JSON);
+
+ final net.minidev.json.JSONArray doubleQuoteEqualsResult = ctx.read(DOUBLE_QUOTES_EQUALS_FILTER);
+ assertEquals("bar", doubleQuoteEqualsResult.get(0));
+
+ final net.minidev.json.JSONArray singleQuoteEqualsResult = ctx.read(SINGLE_QUOTES_EQUALS_FILTER);
+ assertEquals(doubleQuoteEqualsResult, singleQuoteEqualsResult);
+
+ final net.minidev.json.JSONArray doubleQuoteInResult = ctx.read(DOUBLE_QUOTES_IN_FILTER);
+ assertEquals(doubleQuoteInResult, doubleQuoteEqualsResult);
+
+ final net.minidev.json.JSONArray singleQuoteInResult = ctx.read(SINGLE_QUOTES_IN_FILTER);
+ assertEquals(doubleQuoteInResult, singleQuoteInResult);
+ }
+}
\ No newline at end of file
diff --git a/json-path/src/test/java/com/jayway/jsonpath/old/IssuesTest.java b/json-path/src/test/java/com/jayway/jsonpath/old/IssuesTest.java
index 93d3f8b2..04ffa00f 100644
--- a/json-path/src/test/java/com/jayway/jsonpath/old/IssuesTest.java
+++ b/json-path/src/test/java/com/jayway/jsonpath/old/IssuesTest.java
@@ -999,4 +999,24 @@ public class IssuesTest extends BaseTest {
assertThat(objectNode.get("can delete").isNull());
assertThat(objectNode.get("can't delete").isNull());
}
+
+ @Test
+ public void issue_309(){
+
+ String json = "{\n" +
+ "\"jsonArr\": [\n" +
+ " {\n" +
+ " \"name\":\"nOne\"\n" +
+ " },\n" +
+ " {\n" +
+ " \"name\":\"nTwo\"\n" +
+ " }\n" +
+ " ]\n" +
+ "}";
+
+ DocumentContext doc = JsonPath.parse(json).set("$.jsonArr[1].name", "Jayway");
+
+ assertThat(doc.read("$.jsonArr[0].name")).isEqualTo("nOne");
+ assertThat(doc.read("$.jsonArr[1].name")).isEqualTo("Jayway");
+ }
}
diff --git a/json-path/src/test/java/com/jayway/jsonpath/old/internal/ArrayIndexFilterTest.java b/json-path/src/test/java/com/jayway/jsonpath/old/internal/ArrayIndexFilterTest.java
index 434775af..eaada166 100644
--- a/json-path/src/test/java/com/jayway/jsonpath/old/internal/ArrayIndexFilterTest.java
+++ b/json-path/src/test/java/com/jayway/jsonpath/old/internal/ArrayIndexFilterTest.java
@@ -1,6 +1,9 @@
package com.jayway.jsonpath.old.internal;
import com.jayway.jsonpath.JsonPath;
+
+import org.junit.Assert;
+
import org.hamcrest.Matchers;
import org.junit.Test;
@@ -43,4 +46,10 @@ public class ArrayIndexFilterTest {
assertThat(result, Matchers.contains(1, 3, 5));
}
+ @Test
+ public void can_access_items_from_end_with_negative_index() {
+ int result = JsonPath.parse(JSON).read("$[-3]");
+ Assert.assertEquals(8, result);
+ }
+
}