From 393926ad96627f3ce9526400d34b54544e28bb2c Mon Sep 17 00:00:00 2001 From: Kalle Stenflo Date: Tue, 30 Sep 2014 10:49:02 +0200 Subject: [PATCH] Fixed issue with indefinite path throwing exception. --- .../main/java/com/jayway/jsonpath/Option.java | 26 +++++- .../jsonpath/internal/token/PathToken.java | 31 ++++++- .../internal/token/WildcardPathToken.java | 3 +- .../java/com/jayway/jsonpath/OptionsTest.java | 21 +++++ .../com/jayway/jsonpath/old/IssuesTest.java | 26 ++++-- .../com/jayway/jsonpath/old/JsonPathTest.java | 81 +++++-------------- .../jayway/jsonpath/old/JsonProviderTest.java | 2 - .../jsonpath/old/MultiAttributeTest.java | 44 ---------- 8 files changed, 118 insertions(+), 116 deletions(-) delete mode 100644 json-path/src/test/java/com/jayway/jsonpath/old/MultiAttributeTest.java diff --git a/json-path/src/main/java/com/jayway/jsonpath/Option.java b/json-path/src/main/java/com/jayway/jsonpath/Option.java index 3235fea9..04f0a323 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/Option.java +++ b/json-path/src/main/java/com/jayway/jsonpath/Option.java @@ -58,7 +58,31 @@ public enum Option { * If an exception is thrown and the option {@link Option#ALWAYS_RETURN_LIST} an empty list is returned. * If an exception is thrown and the option {@link Option#ALWAYS_RETURN_LIST} is not present null is returned. */ - SUPPRESS_EXCEPTIONS + SUPPRESS_EXCEPTIONS, + /** + * Configures JsonPath to require properties defined in path when an indefinite path is evaluated. + * + * + * Given: + * + *
+     * [
+     *     {
+     *         "a" : "a-val",
+     *         "b" : "b-val"
+     *     },
+     *     {
+     *         "a" : "a-val",
+     *     }
+     * ]
+     * 
+ * + * evaluating the path "$[*].b" + * + * If REQUIRE_PATH_PROPERTIES option is present PathNotFoundException is thrown. + * If REQUIRE_PATH_PROPERTIES option is not present ["b-val"] is returned. + */ + REQUIRE_PATH_PROPERTIES } diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/token/PathToken.java b/json-path/src/main/java/com/jayway/jsonpath/internal/token/PathToken.java index 66c5d151..cfc50de4 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/token/PathToken.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/token/PathToken.java @@ -24,11 +24,14 @@ import java.util.List; public abstract class PathToken { + private PathToken prev; private PathToken next; private Boolean definite = null; + private Boolean upstreamDefinite = null; PathToken appendTailToken(PathToken next) { this.next = next; + this.next.prev = this; return next; } @@ -43,7 +46,7 @@ public abstract class PathToken { if(ctx.options().contains(Option.DEFAULT_PATH_LEAF_TO_NULL)){ propertyVal = null; } else { - if(ctx.options().contains(Option.SUPPRESS_EXCEPTIONS)){ + if(ctx.options().contains(Option.SUPPRESS_EXCEPTIONS) && !ctx.options().contains(Option.REQUIRE_PATH_PROPERTIES)){ return; } else { throw new PathNotFoundException("No results for path: " + evalPath); @@ -51,7 +54,13 @@ public abstract class PathToken { } } else { - throw new PathNotFoundException(); + if(!isUpstreamDefinite() && + !ctx.options().contains(Option.REQUIRE_PATH_PROPERTIES) && + !ctx.options().contains(Option.SUPPRESS_EXCEPTIONS)){ + return; + } else { + throw new PathNotFoundException(evalPath); + } } } if (isLeaf()) { @@ -109,6 +118,9 @@ public abstract class PathToken { } } + PathToken prev(){ + return prev; + } PathToken next() { if (isLeaf()) { @@ -121,6 +133,21 @@ public abstract class PathToken { return next == null; } + boolean isRoot() { + return prev == null; + } + + boolean isUpstreamDefinite(){ + if(upstreamDefinite != null){ + return upstreamDefinite.booleanValue(); + } + boolean isUpstreamDefinite = isTokenDefinite(); + if (isUpstreamDefinite && !isRoot()) { + isUpstreamDefinite = prev.isPathDefinite(); + } + upstreamDefinite = isUpstreamDefinite; + return isUpstreamDefinite; + } public int getTokenCount() { int cnt = 1; diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/token/WildcardPathToken.java b/json-path/src/main/java/com/jayway/jsonpath/internal/token/WildcardPathToken.java index 02966d3e..742b2787 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/token/WildcardPathToken.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/token/WildcardPathToken.java @@ -14,6 +14,7 @@ */ package com.jayway.jsonpath.internal.token; +import com.jayway.jsonpath.Option; import com.jayway.jsonpath.PathNotFoundException; import static java.util.Arrays.asList; @@ -34,7 +35,7 @@ public class WildcardPathToken extends PathToken { try { handleArrayIndex(idx, currentPath, model, ctx); } catch (PathNotFoundException p){ - if(!isLeaf() && !next().isLeaf()){ + if(ctx.options().contains(Option.REQUIRE_PATH_PROPERTIES)){ throw p; } } diff --git a/json-path/src/test/java/com/jayway/jsonpath/OptionsTest.java b/json-path/src/test/java/com/jayway/jsonpath/OptionsTest.java index c480a48d..309cc997 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/OptionsTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/OptionsTest.java @@ -2,13 +2,17 @@ package com.jayway.jsonpath; import org.junit.Test; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import static com.jayway.jsonpath.JsonPath.using; import static com.jayway.jsonpath.Option.*; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonMap; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; public class OptionsTest extends BaseTest { @@ -76,4 +80,21 @@ public class OptionsTest extends BaseTest { assertThat((List)result).containsOnly("a", "b"); } + @Test + public void when_property_is_required_exception_is_thrown() { + List> model = asList(singletonMap("a", "a-val"),singletonMap("b", "b-val")); + + Configuration conf = Configuration.defaultConfiguration(); + + assertThat(using(conf).parse(model).read("$[*].a", List.class)).containsExactly("a-val"); + + + conf = conf.addOptions(Option.REQUIRE_PATH_PROPERTIES); + + try{ + using(conf).parse(model).read("$[*].a", List.class); + fail("Should throw PathNotFoundException"); + } catch (PathNotFoundException pnf){} + } + } 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 e74e1ee9..6912a53c 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 @@ -163,12 +163,9 @@ public class IssuesTest { " }\n" + "]"; - List result = read(json, "$.[?(@.compatible == true)].sku"); + List result = read(json, "$.[?(@.compatible == true)].sku"); - System.out.println(result); - - //assertThat(result.getValue(0), is(new Double(10.1))); - //assertThat(result.getValue(1), is(new Double(21.0))); + Assertions.assertThat(result).containsExactly("SKU-005", "SKU-003"); } @@ -326,8 +323,8 @@ public class IssuesTest { List> result = read(json, "$.store.book[?(@.author.age == 36)]"); - System.out.println(result); - + Assertions.assertThat(result).hasSize(1); + Assertions.assertThat(result.get(0)).containsEntry("title", "Sayings of the Century"); } @Test @@ -429,6 +426,21 @@ public class IssuesTest { } catch (PathNotFoundException e){ Assertions.assertThat(e).hasMessage("No results for path: $['a']['x']"); } + } + + @Test + public void issue_x() { + + String json = "{\n" + + " \"a\" : [\n" + + " {},\n" + + " { \"b\" : [ { \"c\" : \"foo\"} ] }\n" + + " ]\n" + + "}\n"; + + List result = JsonPath.read(json, "$.a.*.b.*.c"); + + Assertions.assertThat(result).containsExactly("foo"); } } diff --git a/json-path/src/test/java/com/jayway/jsonpath/old/JsonPathTest.java b/json-path/src/test/java/com/jayway/jsonpath/old/JsonPathTest.java index 27131beb..8c702de2 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/old/JsonPathTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/old/JsonPathTest.java @@ -3,6 +3,7 @@ package com.jayway.jsonpath.old; import com.jayway.jsonpath.BaseTest; import com.jayway.jsonpath.Configuration; import com.jayway.jsonpath.JsonPath; +import com.jayway.jsonpath.Option; import com.jayway.jsonpath.PathNotFoundException; import com.jayway.jsonpath.internal.PathCompiler; import org.assertj.core.api.Assertions; @@ -55,7 +56,7 @@ public class JsonPathTest extends BaseTest { " \"display-price\": 19.95,\n" + " \"foo:bar\": \"fooBar\",\n" + " \"dot.notation\": \"new\",\n" + - " \"dash-notation\": \"dashes\"\n" + + " \"dash-notation\": \"dashes\"\n" + " }\n" + " }\n" + "}"; @@ -84,8 +85,7 @@ public class JsonPathTest extends BaseTest { //Object read = JsonPath.using(Configuration.defaultConfiguration().setOptions(Option.THROW_ON_MISSING_PROPERTY)).parse(DOCUMENT).read("$.store.book[*].fooBar"); //Object read = JsonPath.using(Configuration.defaultConfiguration()).parse(DOCUMENT).read("$.store.book[*].fooBar"); - Object read2 = JsonPath.using(Configuration.defaultConfiguration()).parse(DOCUMENT).read("$.store.book[*].fooBar.not"); - + Object read2 = JsonPath.using(Configuration.defaultConfiguration().addOptions(Option.REQUIRE_PATH_PROPERTIES)).parse(DOCUMENT).read("$.store.book[*].fooBar.not"); } @@ -127,20 +127,16 @@ public class JsonPathTest extends BaseTest { " },\n" + " \"version\": 1371160528774\n" + "}"; - - -// System.out.println(JsonPath.read(json, "$.data.passes[0].id")); - - - - System.out.println(JsonPath.isPathDefinite("$.data.passes[0].id")); - - System.out.println(JsonPath.read(json, "$.data2.passes[0].id")); - System.out.println(JsonPath.isPathDefinite("$.data2.passes[0].id")); - + try { + JsonPath.read(json, "$.data.passes[0].id"); + Assertions.fail("Expected PathNotFoundException"); + } catch (PathNotFoundException e) { + } + Assertions.assertThat(JsonPath.read(json, "$.data2.passes[0].id")).isEqualTo("1"); } - @Test public void array_start_expands() throws Exception { + @Test + public void array_start_expands() throws Exception { //assertThat(JsonPath.>read(ARRAY_EXPAND, "$[?(@.parent = 'ONE')].child.name"), hasItems("NAME_ONE")); assertThat(JsonPath.>read(ARRAY_EXPAND, "$[?(@['parent'] == 'ONE')].child.name"), hasItems("NAME_ONE")); } @@ -148,19 +144,16 @@ public class JsonPathTest extends BaseTest { @Test public void bracket_notation_can_be_used_in_path() throws Exception { - //System.out.println(ScriptEngineJsonPath.eval(DOCUMENT, "$.['store'].['bicycle'].['dot.notation']")); - - assertEquals("new", JsonPath.read(DOCUMENT, "$.['store'].bicycle.['dot.notation']")); assertEquals("new", JsonPath.read(DOCUMENT, "$['store']['bicycle']['dot.notation']")); assertEquals("new", JsonPath.read(DOCUMENT, "$.['store']['bicycle']['dot.notation']")); assertEquals("new", JsonPath.read(DOCUMENT, "$.['store'].['bicycle'].['dot.notation']")); - assertEquals("dashes", JsonPath.read(DOCUMENT, "$.['store'].bicycle.['dash-notation']")); - assertEquals("dashes", JsonPath.read(DOCUMENT, "$['store']['bicycle']['dash-notation']")); - assertEquals("dashes", JsonPath.read(DOCUMENT, "$.['store']['bicycle']['dash-notation']")); - assertEquals("dashes", JsonPath.read(DOCUMENT, "$.['store'].['bicycle'].['dash-notation']")); + assertEquals("dashes", JsonPath.read(DOCUMENT, "$.['store'].bicycle.['dash-notation']")); + assertEquals("dashes", JsonPath.read(DOCUMENT, "$['store']['bicycle']['dash-notation']")); + assertEquals("dashes", JsonPath.read(DOCUMENT, "$.['store']['bicycle']['dash-notation']")); + assertEquals("dashes", JsonPath.read(DOCUMENT, "$.['store'].['bicycle'].['dash-notation']")); } @Test @@ -168,7 +161,6 @@ public class JsonPathTest extends BaseTest { List matches = JsonPath.read(ARRAY, "$.[?(@.value == 1)]"); assertEquals(1, matches.size()); - System.out.println(matches); } @Test @@ -176,13 +168,10 @@ public class JsonPathTest extends BaseTest { Integer matches = JsonPath.read(ARRAY, "$.[1].value"); assertEquals(new Integer(2), matches); - System.out.println(matches); } @Test public void read_path_with_colon() throws Exception { - //TODO: breaking v2 - //assertEquals(JsonPath.read(DOCUMENT, "$.store.bicycle.foo:bar"), "fooBar"); assertEquals(JsonPath.read(DOCUMENT, "$['store']['bicycle']['foo:bar']"), "fooBar"); } @@ -209,6 +198,7 @@ public class JsonPathTest extends BaseTest { JsonPath path = JsonPath.compile("$.store.book[*]"); List list = path.read(DOCUMENT); + Assertions.assertThat(list.size()).isEqualTo(4); } @@ -244,31 +234,24 @@ public class JsonPathTest extends BaseTest { @Test public void all_prices_in_store() throws Exception { - assertThat(JsonPath.>read(DOCUMENT, "$.store..['display-price']"), hasItems(8.95D, 12.99D, 8.99D, 19.95D)); } @Test public void access_array_by_index_from_tail() throws Exception { - //TODO: breaking change - //assertThat(JsonPath.read(DOCUMENT, "$..book[(@.length-1)].author"), equalTo("J. R. R. Tolkien")); - //assertThat(JsonPath.read(DOCUMENT, "$..book[1:].author"), equalTo("J. R. R. Tolkien")); - assertThat(JsonPath.>read(DOCUMENT, "$..book[(@.length-1)].author"), hasItems("J. R. R. Tolkien")); - assertThat(JsonPath.>read(DOCUMENT, "$..book[1:].author"), hasItems("Evelyn Waugh","Herman Melville","J. R. R. Tolkien")); + assertThat(JsonPath.>read(DOCUMENT, "$..book[1:].author"), hasItems("Evelyn Waugh", "Herman Melville", "J. R. R. Tolkien")); } @Test public void read_store_book_index_0_and_1() throws Exception { - assertThat(JsonPath.>read(DOCUMENT, "$.store.book[0,1].author"), hasItems("Nigel Rees", "Evelyn Waugh")); assertTrue(JsonPath.read(DOCUMENT, "$.store.book[0,1].author").size() == 2); } @Test public void read_store_book_pull_first_2() throws Exception { - assertThat(JsonPath.>read(DOCUMENT, "$.store.book[:2].author"), hasItems("Nigel Rees", "Evelyn Waugh")); assertTrue(JsonPath.read(DOCUMENT, "$.store.book[:2].author").size() == 2); } @@ -276,52 +259,36 @@ public class JsonPathTest extends BaseTest { @Test public void read_store_book_filter_by_isbn() throws Exception { - assertThat(JsonPath.>read(DOCUMENT, "$.store.book[?(@.isbn)].isbn"), hasItems("0-553-21311-3", "0-395-19395-8")); assertTrue(JsonPath.read(DOCUMENT, "$.store.book[?(@.isbn)].isbn").size() == 2); - assertTrue(JsonPath.read(DOCUMENT, "$.store.book[?(@['isbn'])].isbn").size() == 2); + assertTrue(JsonPath.read(DOCUMENT, "$.store.book[?(@['isbn'])].isbn").size() == 2); } @Test public void all_books_cheaper_than_10() throws Exception { - - assertThat(JsonPath.>read(DOCUMENT, "$..book[?(@['display-price'] < 10)].title"), hasItems("Sayings of the Century", "Moby Dick")); assertThat(JsonPath.>read(DOCUMENT, "$..book[?(@.display-price < 10)].title"), hasItems("Sayings of the Century", "Moby Dick")); - } - @Test + @Test public void all_books() throws Exception { - - //List books = JsonPath.>read(DOCUMENT, "$..book"); - Object books = JsonPath.>read(DOCUMENT, "$..book"); - - System.out.println(books); - - } + Assertions.assertThat(JsonPath.>read(DOCUMENT, "$..book")).hasSize(1); + } @Test public void dot_in_predicate_works() throws Exception { - assertThat(JsonPath.>read(PRODUCT_JSON, "$.product[?(@.version=='4.0')].codename"), hasItems("Montreal")); - } @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")); - } @Test public void all_books_with_category_reference() throws Exception { - assertThat(JsonPath.>read(DOCUMENT, "$..book[?(@.category=='reference')].title"), hasItems("Sayings of the Century")); assertThat(JsonPath.>read(DOCUMENT, "$.store.book[?(@.category=='reference')].title"), hasItems("Sayings of the Century")); - } @Test @@ -331,15 +298,11 @@ public class JsonPathTest extends BaseTest { @Test(expected = PathNotFoundException.class) public void access_index_out_of_bounds_does_not_throw_exception() throws Exception { - - Object res = JsonPath.read(DOCUMENT, "$.store.book[100].author"); - + JsonPath.read(DOCUMENT, "$.store.book[100].author"); } - @Test public void exists_filter_with_nested_path() throws Exception { - assertThat(JsonPath.>read(DOCUMENT, "$..[?(@.bicycle.color)]"), hasSize(1)); assertThat(JsonPath.>read(DOCUMENT, "$..[?(@.bicycle.numberOfGears)]"), hasSize(0)); diff --git a/json-path/src/test/java/com/jayway/jsonpath/old/JsonProviderTest.java b/json-path/src/test/java/com/jayway/jsonpath/old/JsonProviderTest.java index 5bf18e46..7cde8216 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/old/JsonProviderTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/old/JsonProviderTest.java @@ -54,7 +54,6 @@ public class JsonProviderTest { Object o = provider.parse(DOCUMENT); - System.out.println(o); } @@ -64,6 +63,5 @@ public class JsonProviderTest { Object o = provider.parse(ARRAY); - System.out.println(o); } } diff --git a/json-path/src/test/java/com/jayway/jsonpath/old/MultiAttributeTest.java b/json-path/src/test/java/com/jayway/jsonpath/old/MultiAttributeTest.java deleted file mode 100644 index bd30d392..00000000 --- a/json-path/src/test/java/com/jayway/jsonpath/old/MultiAttributeTest.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.jayway.jsonpath.old; - -public class MultiAttributeTest { - - public final static String DOCUMENT = - "{ \"store\": {\n" + - " \"book\": [ \n" + - " { \"category\": \"reference\",\n" + - " \"author\": \"Nigel Rees\",\n" + - " \"title\": \"Sayings of the Century\",\n" + - " \"display-price\": 8.95\n" + - " },\n" + - " { \"category\": \"fiction\",\n" + - " \"author\": \"Evelyn Waugh\",\n" + - " \"title\": \"Sword of Honour\",\n" + - " \"display-price\": 12.99\n" + - " },\n" + - " { \"category\": \"fiction\",\n" + - " \"author\": \"Herman Melville\",\n" + - " \"title\": \"Moby Dick\",\n" + - " \"isbn\": \"0-553-21311-3\",\n" + - " \"display-price\": 8.99\n" + - " },\n" + - " { \"category\": \"fiction\",\n" + - " \"author\": \"J. R. R. Tolkien\",\n" + - " \"title\": \"The Lord of the Rings\",\n" + - " \"isbn\": \"0-395-19395-8\",\n" + - " \"display-price\": 22.99\n" + - " }\n" + - " ],\n" + - " \"bicycle\": {\n" + - " \"color\": \"red\",\n" + - " \"display-price\": 19.95,\n" + - " \"foo:bar\": \"fooBar\",\n" + - " \"dot.notation\": \"new\",\n" + - " \"dash-notation\": \"dashes\"\n" + - " }\n" + - " }\n" + - "}"; - - - - -}