From 81aa8b171d4caaf00f33c16ce3762b23f51a20b7 Mon Sep 17 00:00:00 2001 From: Kalle Stenflo Date: Wed, 21 Aug 2013 15:15:05 +0200 Subject: [PATCH] Array eval filters can be a path like [?(@.address.street == 'foo')] --- .../java/com/jayway/jsonpath/JsonPath.java | 5 + .../internal/filter/ArrayEvalFilter.java | 101 +++++++++++------- .../jsonpath/internal/filter/FieldFilter.java | 4 +- .../java/com/jayway/jsonpath/IssuesTest.java | 39 +++++-- .../com/jayway/jsonpath/JsonPathTest.java | 4 +- .../internal/filter/ArrayEvalFilterTest.java | 42 ++++++++ 6 files changed, 147 insertions(+), 48 deletions(-) create mode 100644 json-path/src/test/java/com/jayway/jsonpath/internal/filter/ArrayEvalFilterTest.java diff --git a/json-path/src/main/java/com/jayway/jsonpath/JsonPath.java b/json-path/src/main/java/com/jayway/jsonpath/JsonPath.java index c20c779f..5b2cc906 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/JsonPath.java +++ b/json-path/src/main/java/com/jayway/jsonpath/JsonPath.java @@ -208,6 +208,11 @@ public class JsonPath { JsonProvider jsonProvider = JsonProviderFactory.createProvider(); + if(this.getPath().equals("$")){ + //This path only references the whole object. No need to do any work here... + return (T)jsonObject; + } + if (!jsonProvider.isMap(jsonObject) && !jsonProvider.isList(jsonObject)) { throw new IllegalArgumentException("Invalid container object"); } diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ArrayEvalFilter.java b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ArrayEvalFilter.java index eb00d0fd..e3259893 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ArrayEvalFilter.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ArrayEvalFilter.java @@ -15,6 +15,8 @@ package com.jayway.jsonpath.internal.filter; import com.jayway.jsonpath.InvalidPathException; +import com.jayway.jsonpath.JsonPath; +import com.jayway.jsonpath.PathNotFoundException; import com.jayway.jsonpath.internal.filter.eval.ExpressionEvaluator; import com.jayway.jsonpath.spi.JsonProvider; @@ -28,25 +30,14 @@ import java.util.regex.Pattern; */ public class ArrayEvalFilter extends PathTokenFilter { - private static final Pattern PATTERN = Pattern.compile("(.*?)\\s?([=<>]+)\\s?(.*)"); + private static final Pattern PATTERN = Pattern.compile("\\[\\s?\\?\\(\\s?(@.*?)\\s?([!=<>]+)\\s?(.*?)\\s?\\)\\s?\\]"); + private final ConditionStatement conditionStatement; public ArrayEvalFilter(String condition) { super(condition); //[?(@.isbn == 10)] - - String trimmedCondition = condition; - - if(condition.contains("['")){ - trimmedCondition = trimmedCondition.replace("['", "."); - trimmedCondition = trimmedCondition.replace("']", ""); - } - if(trimmedCondition.startsWith("[?(@==")){ - trimmedCondition = trim(trimmedCondition, 4, 2); - } else { - trimmedCondition = trim(trimmedCondition, 5, 2); - } - this.conditionStatement = createConditionStatement(trimmedCondition); + this.conditionStatement = createConditionStatement(condition); } @Override @@ -78,34 +69,24 @@ public class ArrayEvalFilter extends PathTokenFilter { } private boolean isMatch(Object check, ConditionStatement conditionStatement, JsonProvider jsonProvider) { - if (jsonProvider.isMap(check)) { - Map obj = jsonProvider.toMap(check); - - if (!obj.containsKey(conditionStatement.getField())) { - return false; - } - Object propertyValue = obj.get(conditionStatement.getField()); - - if (jsonProvider.isContainer(propertyValue)) { - return false; - } - return ExpressionEvaluator.eval(propertyValue, conditionStatement.getOperator(), conditionStatement.getExpected()); - } else if(jsonProvider.isList(check)) { + try { + Object value = conditionStatement.path.read(check); + return ExpressionEvaluator.eval(value, conditionStatement.getOperator(), conditionStatement.getExpected()); + } catch (PathNotFoundException e){ return false; - } else { - return ExpressionEvaluator.eval(check, conditionStatement.getOperator(), conditionStatement.getExpected()); + } catch (RuntimeException e){ + e.printStackTrace(); + throw new RuntimeException(e); } - } - - private ConditionStatement createConditionStatement(String str) { + static ConditionStatement createConditionStatement(String str) { Matcher matcher = PATTERN.matcher(str); if (matcher.matches()) { - String property = matcher.group(1); - String operator = matcher.group(2); - String expected = matcher.group(3); + String property = matcher.group(1).trim(); + String operator = matcher.group(2).trim(); + String expected = matcher.group(3).trim(); return new ConditionStatement(property, operator, expected); } else { @@ -113,14 +94,16 @@ public class ArrayEvalFilter extends PathTokenFilter { } } - private static class ConditionStatement { + static class ConditionStatement { private final String field; private final String operator; private final String expected; + private final JsonPath path; + - private ConditionStatement(String field, String operator, String expected) { + ConditionStatement(String field, String operator, String expected) { this.field = field; - this.operator = operator.trim(); + this.operator = operator; if(expected.startsWith("'")){ @@ -128,6 +111,17 @@ public class ArrayEvalFilter extends PathTokenFilter { }else{ this.expected = expected; } + + if(field.startsWith("@.")){ + this.path = JsonPath.compile(this.field.replace("@.", "$.")); + } else { + this.path = JsonPath.compile(this.field.replace("@", "$")); + } + + } + + public JsonPath getJsonPath() { + return path; } public String getField() { @@ -141,5 +135,36 @@ public class ArrayEvalFilter extends PathTokenFilter { public String getExpected() { return expected; } + + @Override + public String toString() { + return "ConditionStatement{" + + "field='" + field + '\'' + + ", operator='" + operator + '\'' + + ", expected='" + expected + '\'' + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ConditionStatement that = (ConditionStatement) o; + + if (expected != null ? !expected.equals(that.expected) : that.expected != null) return false; + if (field != null ? !field.equals(that.field) : that.field != null) return false; + if (operator != null ? !operator.equals(that.operator) : that.operator != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = field != null ? field.hashCode() : 0; + result = 31 * result + (operator != null ? operator.hashCode() : 0); + result = 31 * result + (expected != null ? expected.hashCode() : 0); + return result; + } } } diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/FieldFilter.java b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/FieldFilter.java index 95dba773..a43674e9 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/FieldFilter.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/FieldFilter.java @@ -70,7 +70,7 @@ public class FieldFilter extends PathTokenFilter { } return result; } - } else { + } else if (jsonProvider.isMap(obj)){ Map map = jsonProvider.toMap(obj); if(!map.containsKey(condition) && split.length == 1){ @@ -91,6 +91,8 @@ public class FieldFilter extends PathTokenFilter { } + } else { + throw new PathNotFoundException(); } } diff --git a/json-path/src/test/java/com/jayway/jsonpath/IssuesTest.java b/json-path/src/test/java/com/jayway/jsonpath/IssuesTest.java index c7351908..89ade36b 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/IssuesTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/IssuesTest.java @@ -1,15 +1,12 @@ package com.jayway.jsonpath; import com.jayway.jsonpath.internal.IOUtils; -import com.jayway.jsonpath.spi.JsonProviderFactory; -import com.jayway.jsonpath.spi.impl.JacksonProvider; import org.hamcrest.Matchers; -import org.junit.Assert; import org.junit.Test; import java.io.InputStream; import java.util.List; -import java.util.regex.Matcher; +import java.util.Map; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; @@ -161,12 +158,40 @@ public class IssuesTest { @Test public void issue_32(){ - - - String json = "{\"text\" : \"skill: \\\"Heuristic Evaluation\\\"\", \"country\" : \"\"}"; assertEquals("skill: \"Heuristic Evaluation\"", JsonPath.read(json, "$.text")); } + @Test + public void issue_33(){ + String json = "{ \"store\": {\n" + + " \"book\": [ \n" + + " { \"category\": \"reference\",\n" + + " \"author\": {\n" + + " \"name\": \"Author Name\",\n" + + " \"age\": 36\n" + + " },\n" + + " \"title\": \"Sayings of the Century\",\n" + + " \"price\": 8.95\n" + + " },\n" + + " { \"category\": \"fiction\",\n" + + " \"author\": \"Evelyn Waugh\",\n" + + " \"title\": \"Sword of Honour\",\n" + + " \"price\": 12.99,\n" + + " \"isbn\": \"0-553-21311-3\"\n" + + " }\n" + + " ],\n" + + " \"bicycle\": {\n" + + " \"color\": \"red\",\n" + + " \"price\": 19.95\n" + + " }\n" + + " }\n" + + "}"; + + List> result = JsonPath.read(json, "$.store.book[?(@.author.age == 36)]"); + + System.out.println(result); + + } } diff --git a/json-path/src/test/java/com/jayway/jsonpath/JsonPathTest.java b/json-path/src/test/java/com/jayway/jsonpath/JsonPathTest.java index d91c02df..4fde20ce 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/JsonPathTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/JsonPathTest.java @@ -1,6 +1,5 @@ package com.jayway.jsonpath; -import com.jayway.jsonpath.spi.JsonProviderFactory; import com.jayway.jsonpath.util.ScriptEngineJsonPath; import org.junit.Test; @@ -245,7 +244,8 @@ public class JsonPathTest { @Test public void dots_in_predicate_works() throws Exception { - assertThat(JsonPath.>read(PRODUCT_JSON, "$.product[?(@.attr.with.dot=='A')].codename"), hasItems("Seattle")); + assertThat(JsonPath.>read(PRODUCT_JSON, "$.product[?(@.['attr.with.dot']=='A')].codename"), hasItems("Seattle")); + //assertThat(JsonPath.>read(PRODUCT_JSON, "$.product[?(@.attr.with.dot=='A')].codename"), hasItems("Seattle")); } diff --git a/json-path/src/test/java/com/jayway/jsonpath/internal/filter/ArrayEvalFilterTest.java b/json-path/src/test/java/com/jayway/jsonpath/internal/filter/ArrayEvalFilterTest.java new file mode 100644 index 00000000..80770dd5 --- /dev/null +++ b/json-path/src/test/java/com/jayway/jsonpath/internal/filter/ArrayEvalFilterTest.java @@ -0,0 +1,42 @@ +package com.jayway.jsonpath.internal.filter; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * User: kalle + * Date: 8/21/13 + * Time: 12:21 PM + */ +public class ArrayEvalFilterTest { + + @Test + public void condition_statements_can_be_parsed() { + + //int array + assertEquals(new ArrayEvalFilter.ConditionStatement("@", "==", "5"), ArrayEvalFilter.createConditionStatement("[?(@==5)]")); + assertEquals(new ArrayEvalFilter.ConditionStatement("@", "==", "5"), ArrayEvalFilter.createConditionStatement("[?(@ == 5)]")); + assertEquals(new ArrayEvalFilter.ConditionStatement("@", "==", "5"), ArrayEvalFilter.createConditionStatement("[ ?(@ == 5) ]")); + assertEquals(new ArrayEvalFilter.ConditionStatement("@", "==", "5"), ArrayEvalFilter.createConditionStatement("[ ?( @ == 5) ]")); + assertEquals(new ArrayEvalFilter.ConditionStatement("@", "==", "5"), ArrayEvalFilter.createConditionStatement("[ ?( @ == 5 ) ]")); + + //String array + assertEquals(new ArrayEvalFilter.ConditionStatement("@", "==", "one"), ArrayEvalFilter.createConditionStatement("[?(@=='one')]")); + assertEquals(new ArrayEvalFilter.ConditionStatement("@", "==", "one monkey"), ArrayEvalFilter.createConditionStatement("[?(@ == 'one monkey')]")); + assertEquals(new ArrayEvalFilter.ConditionStatement("@", "==", "two"), ArrayEvalFilter.createConditionStatement("[?(@ == 'two')]")); + + //Sub item dot notation + assertEquals(new ArrayEvalFilter.ConditionStatement("@.name", "==", "true"), ArrayEvalFilter.createConditionStatement("[?(@.name == true)]")); + + //Sub item bracket notation + assertEquals(new ArrayEvalFilter.ConditionStatement("@['name']", "==", "true"), ArrayEvalFilter.createConditionStatement("[?(@['name'] == true)]")); + assertEquals(new ArrayEvalFilter.ConditionStatement("@.['name']", "==", "true"), ArrayEvalFilter.createConditionStatement("[?(@.['name'] == true)]")); + + //Sub path notation + assertEquals(new ArrayEvalFilter.ConditionStatement("@['name']['age']", "!=", "true"), ArrayEvalFilter.createConditionStatement("[?(@['name']['age'] != true)]")); + assertEquals(new ArrayEvalFilter.ConditionStatement("@.['name'].age", ">", "true"), ArrayEvalFilter.createConditionStatement("[?(@.['name'].age > true)]")); + + } + +}