Browse Source

Merge branch 'master' into use-json-smart-for-query-parsing

pull/314/head
jochenberger 8 years ago committed by GitHub
parent
commit
a10b87f055
  1. 2
      LICENSE
  2. 1
      README.md
  3. 2
      build.gradle
  4. 5
      json-path/build.gradle
  5. 36
      json-path/src/main/java/com/jayway/jsonpath/internal/CharacterIndex.java
  6. 18
      json-path/src/main/java/com/jayway/jsonpath/internal/filter/FilterCompiler.java
  7. 2
      json-path/src/main/java/com/jayway/jsonpath/internal/filter/ValueNode.java
  8. 2
      json-path/src/main/java/com/jayway/jsonpath/internal/path/ArrayIndexOperation.java
  9. 25
      json-path/src/main/java/com/jayway/jsonpath/internal/path/PathCompiler.java
  10. 3
      json-path/src/main/java/com/jayway/jsonpath/internal/path/PathToken.java
  11. 4
      json-path/src/main/java/com/jayway/jsonpath/spi/json/JsonOrgJsonProvider.java
  12. 7
      json-path/src/test/java/com/jayway/jsonpath/FilterTest.java
  13. 8
      json-path/src/test/java/com/jayway/jsonpath/JsonOrgJsonProviderTest.java
  14. 128
      json-path/src/test/java/com/jayway/jsonpath/ProviderInTest.java
  15. 20
      json-path/src/test/java/com/jayway/jsonpath/old/IssuesTest.java
  16. 9
      json-path/src/test/java/com/jayway/jsonpath/old/internal/ArrayIndexFilterTest.java

2
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.

1
README.md

@ -155,6 +155,7 @@ Given the json
| <a href="http://jsonpath.herokuapp.com/?path=$.store.*" target="_blank">$.store.*</a> | All things, both books and bicycles |
| <a href="http://jsonpath.herokuapp.com/?path=$.store..price" target="_blank">$.store..price</a> | The price of everything |
| <a href="http://jsonpath.herokuapp.com/?path=$..book[2]" target="_blank">$..book[2]</a> | The third book |
| <a href="http://jsonpath.herokuapp.com/?path=$..book[2]" target="_blank">$..book[-2]</a> | The second to last book |
| <a href="http://jsonpath.herokuapp.com/?path=$..book[0,1]" target="_blank">$..book[0,1]</a> | The first two books |
| <a href="http://jsonpath.herokuapp.com/?path=$..book[:2]" target="_blank">$..book[:2]</a> | All books from index 0 (inclusive) until index 2 (exclusive) |
| <a href="http://jsonpath.herokuapp.com/?path=$..book[1:2]" target="_blank">$..book[1:2]</a> | All books from index 1 (inclusive) until index 2 (exclusive) |

2
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',

5
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

36
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;
}
}

18
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() {

2
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;
}

2
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);
}
}

25
json-path/src/main/java/com/jayway/jsonpath/internal/path/PathCompiler.java

@ -45,9 +45,13 @@ public class PathCompiler {
private final LinkedList<Predicate> filterStack;
private final CharacterIndex path;
private PathCompiler(String path, LinkedList<Predicate> filterStack) {
private PathCompiler(String path, LinkedList<Predicate> filterStack){
this(new CharacterIndex(path), filterStack);
}
private PathCompiler(CharacterIndex path, LinkedList<Predicate> 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<Predicate>(asList(filters));
Path p = new PathCompiler(path.trim(), filterStack).compile();
LinkedList<Predicate> filterStack = new LinkedList<Predicate>(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<Parameter> 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

3
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 {

4
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);

7
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

8
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);
}
}

128
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<String> doubleQuoteEqualsResult = ctx.read(DOUBLE_QUOTES_EQUALS_FILTER);
assertEquals(Lists.newArrayList("bar"), doubleQuoteEqualsResult);
final List<String> singleQuoteEqualsResult = ctx.read(SINGLE_QUOTES_EQUALS_FILTER);
assertEquals(doubleQuoteEqualsResult, singleQuoteEqualsResult);
final List<String> 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);
}
}

20
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");
}
}

9
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);
}
}

Loading…
Cancel
Save