From 9b9ad0331e9d899a5ec00d594d1c622c7fa95156 Mon Sep 17 00:00:00 2001 From: Anton Panasenko Date: Tue, 5 Feb 2013 14:28:08 +0200 Subject: [PATCH] Added access array by variable --- .../java/com/jayway/jsonpath/JsonModel.java | 3 +- .../java/com/jayway/jsonpath/JsonPath.java | 3 +- .../jayway/jsonpath/internal/PathToken.java | 8 +-- .../jsonpath/internal/PathTokenizer.java | 17 ++++- .../internal/filter/ArrayEvalFilter.java | 67 ++++++++++++------- .../internal/filter/ArrayIndexFilter.java | 4 +- .../internal/filter/ArrayQueryFilter.java | 6 +- .../jsonpath/internal/filter/FieldFilter.java | 42 +++++++++--- .../internal/filter/FilterFactory.java | 2 + .../internal/filter/HasFieldFilter.java | 4 +- .../internal/filter/PassthroughFilter.java | 4 +- .../internal/filter/PathTokenFilter.java | 8 +-- .../jsonpath/internal/filter/ScanFilter.java | 4 +- .../internal/filter/WildcardFilter.java | 4 +- .../com/jayway/jsonpath/ArraySlicingTest.java | 7 +- .../com/jayway/jsonpath/JsonPathTest.java | 23 ++++++- 16 files changed, 140 insertions(+), 66 deletions(-) diff --git a/json-path/src/main/java/com/jayway/jsonpath/JsonModel.java b/json-path/src/main/java/com/jayway/jsonpath/JsonModel.java index d5bf2cbc..02a6e484 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/JsonModel.java +++ b/json-path/src/main/java/com/jayway/jsonpath/JsonModel.java @@ -575,6 +575,7 @@ public class JsonModel { } Object modelRef = jsonObject; + Object root = jsonObject; if (jsonPath.getTokenizer().size() == 1) { PathToken onlyToken = jsonPath.getTokenizer().iterator().next(); @@ -588,7 +589,7 @@ public class JsonModel { PathToken currentToken; do { currentToken = tokens.poll(); - modelRef = currentToken.apply(modelRef, this.configuration); + modelRef = currentToken.apply(modelRef, root, this.configuration); } while (!tokens.isEmpty()); if (modelRef.getClass().isAssignableFrom(clazz)) { 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 746d02f1..9355e9c0 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/JsonPath.java +++ b/json-path/src/main/java/com/jayway/jsonpath/JsonPath.java @@ -243,6 +243,7 @@ public class JsonPath { Object result = jsonObject; + Object root = jsonObject; boolean inArrayContext = false; @@ -253,7 +254,7 @@ public class JsonPath { LOG.debug("Applying filter: " + filter + " to " + result); } - result = filter.filter(result, configuration, contextFilters, inArrayContext); + result = filter.filter(result, root, configuration, contextFilters, inArrayContext); if(result == null && !pathToken.isEndToken()){ throw new PathNotFoundException("Path token: '" + pathToken.getFragment() + "' not found."); diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/PathToken.java b/json-path/src/main/java/com/jayway/jsonpath/internal/PathToken.java index a991d781..acb120c7 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/PathToken.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/PathToken.java @@ -45,12 +45,12 @@ public class PathToken { return FilterFactory.createFilter(this); } - public Object filter(Object model, Configuration configuration){ - return FilterFactory.createFilter(this).filter(model, configuration); + public Object filter(Object model, Object root, Configuration configuration){ + return FilterFactory.createFilter(this).filter(model, root, configuration); } - public Object apply(Object model, Configuration configuration){ - return FilterFactory.createFilter(this).getRef(model, configuration); + public Object apply(Object model, Object root, Configuration configuration){ + return FilterFactory.createFilter(this).getRef(model, root, configuration); } public String getFragment() { diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/PathTokenizer.java b/json-path/src/main/java/com/jayway/jsonpath/internal/PathTokenizer.java index 36c9e495..0c3cf615 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/PathTokenizer.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/PathTokenizer.java @@ -100,6 +100,10 @@ public class PathTokenizer implements Iterable { return pathChars[index]; } + private char next() { + return pathChars[index+1]; + } + private char poll() { char peek = peek(); index++; @@ -145,9 +149,18 @@ public class PathTokenizer implements Iterable { StringBuilder sb = new StringBuilder(); - while (!isEmpty() && (!isStopChar(peek(), stopChars))) { - if (peek() == '(') { + if (peek() == '[') { + sb.append(poll()); + } + + while (!isEmpty() && (!isStopChar(peek(), stopChars))) { + if (peek() == '[') { + do { + sb.append(poll()); + } while (peek() != ']'); + sb.append(poll()); + } else if (peek() == '(') { do { sb.append(poll()); } while (peek() != ')'); 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 3577dda0..7ee01015 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 @@ -32,31 +32,14 @@ public class ArrayEvalFilter extends PathTokenFilter { private static final Pattern CONDITION_STATEMENT_PATTERN = Pattern.compile("\\[\\s?\\?\\(.*?[!=<>]+.*?\\)\\s?]"); private static final Pattern PATTERN = Pattern.compile("\\s?(@.*?)\\s?([!=<>]+)\\s?(.*?)\\s?"); - - - private ConditionStatement[] conditionStatements; - public ArrayEvalFilter(String condition) { super(condition); - - // [?(@.name == 'Luke Skywalker' && @.occupation == 'Farm boy')] - // [?(@.name == 'Luke Skywalker')] - - condition = condition.trim(); - condition = condition.substring(3, condition.length()-2); - - String[] split = condition.split("&&"); - - conditionStatements = new ConditionStatement[split.length]; - for(int i = 0; i < split.length; i++){ - conditionStatements[i] = createConditionStatement(split[i]); - } } @Override - public Object filter(Object obj, Configuration configuration) { + public Object filter(Object obj, Object root, Configuration configuration) { JsonProvider jsonProvider = configuration.getProvider(); Iterable src = null; try { @@ -66,7 +49,7 @@ public class ArrayEvalFilter extends PathTokenFilter { } Object result = jsonProvider.createArray(); for (Object item : src) { - if (isMatch(item, configuration, conditionStatements)) { + if (isMatch(item, configuration, getConditionStatements(condition, root))) { jsonProvider.setProperty(result, jsonProvider.length(result), item); } } @@ -74,7 +57,7 @@ public class ArrayEvalFilter extends PathTokenFilter { } @Override - public Object getRef(Object obj, Configuration configuration) { + public Object getRef(Object obj, Object root, Configuration configuration) { throw new UnsupportedOperationException(""); } @@ -101,10 +84,39 @@ public class ArrayEvalFilter extends PathTokenFilter { } } + private ConditionStatement[] getConditionStatements(String condition, Object root) { + // [?(@.name == 'Luke Skywalker' && @.occupation == 'Farm boy')] + // [?(@.name == 'Luke Skywalker')] + + condition = condition.trim(); + condition = condition.substring(3, condition.length()-2); + + String[] split = condition.split("&&"); + + ConditionStatement[] conditionStatements = new ConditionStatement[split.length]; + for(int i = 0; i < split.length; i++){ + conditionStatements[i] = createConditionStatement(split[i], root); + } + return conditionStatements; + } + static boolean isConditionStatement(String condition) { return CONDITION_STATEMENT_PATTERN.matcher(condition).matches(); } + static ConditionStatement createConditionStatement(String condition, Object root) { + Matcher matcher = PATTERN.matcher(condition); + if (matcher.matches()) { + String property = matcher.group(1).trim(); + String operator = matcher.group(2).trim(); + String expected = matcher.group(3).trim(); + + return new ConditionStatement(condition, property, operator, expected, root); + } else { + return null; + } + } + static ConditionStatement createConditionStatement(String condition) { Matcher matcher = PATTERN.matcher(condition); if (matcher.matches()) { @@ -112,12 +124,13 @@ public class ArrayEvalFilter extends PathTokenFilter { String operator = matcher.group(2).trim(); String expected = matcher.group(3).trim(); - return new ConditionStatement(condition, property, operator, expected); + return new ConditionStatement(condition, property, operator, expected, ""); } else { return null; } } + static class ConditionStatement { private final String condition; private final String field; @@ -126,14 +139,15 @@ public class ArrayEvalFilter extends PathTokenFilter { private final JsonPath path; - ConditionStatement(String condition, String field, String operator, String expected) { + ConditionStatement(String condition, String field, String operator, String expected, Object root) { this.condition = condition; this.field = field; this.operator = operator; - if(expected.startsWith("'")){ this.expected = trim(expected, 1, 1); + } else if (expected.startsWith("$")) { + this.expected = JsonPath.read(root, expected).toString(); }else{ this.expected = expected; } @@ -144,8 +158,13 @@ public class ArrayEvalFilter extends PathTokenFilter { this.path = JsonPath.compile(this.field.replace("@", "$")); } } + + ConditionStatement(String field, String operator, String expected, Object root) { + this(null, field, operator, expected, root); + } + ConditionStatement(String field, String operator, String expected) { - this(null, field, operator, expected); + this(null, field, operator, expected, ""); } String getCondition() { diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ArrayIndexFilter.java b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ArrayIndexFilter.java index 1f3e754c..145d0b66 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ArrayIndexFilter.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ArrayIndexFilter.java @@ -52,7 +52,7 @@ public class ArrayIndexFilter extends PathTokenFilter { @Override - public Object filter(Object obj, Configuration configuration) { + public Object filter(Object obj, Object root, Configuration configuration) { JsonProvider jsonProvider = configuration.getProvider(); Object result = jsonProvider.createArray(); @@ -138,7 +138,7 @@ public class ArrayIndexFilter extends PathTokenFilter { } @Override - public Object getRef(Object obj, Configuration configuration) { + public Object getRef(Object obj, Object root, Configuration configuration) { if(SINGLE_ARRAY_INDEX_PATTERN.matcher(condition).matches()){ String trimmedCondition = trim(condition, 1, 1); return configuration.getProvider().getProperty(obj, trimmedCondition); diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ArrayQueryFilter.java b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ArrayQueryFilter.java index 56a9b1e2..18fbe2aa 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ArrayQueryFilter.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ArrayQueryFilter.java @@ -29,18 +29,18 @@ public class ArrayQueryFilter extends PathTokenFilter { } @Override - public Object filter(Object obj, Configuration configuration, LinkedList filters, boolean inArrayContext) { + public Object filter(Object obj, Object root, Configuration configuration, LinkedList filters, boolean inArrayContext) { Filter filter = filters.poll(); return filter.doFilter(configuration.getProvider().toIterable(obj), configuration); } @Override - public Object filter(Object obj, Configuration configuration) { + public Object filter(Object obj, Object root, Configuration configuration) { throw new UnsupportedOperationException(); } @Override - public Object getRef(Object obj, Configuration configuration) { + public Object getRef(Object obj, Object root, Configuration configuration) { throw new UnsupportedOperationException(""); } 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 9c013552..6a636cd7 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 @@ -14,10 +14,7 @@ */ package com.jayway.jsonpath.internal.filter; -import com.jayway.jsonpath.Configuration; -import com.jayway.jsonpath.Filter; -import com.jayway.jsonpath.Option; -import com.jayway.jsonpath.PathNotFoundException; +import com.jayway.jsonpath.*; import com.jayway.jsonpath.internal.PathToken; import com.jayway.jsonpath.spi.JsonProvider; @@ -39,10 +36,14 @@ public class FieldFilter extends PathTokenFilter { } @Override - public Object filter(Object obj, Configuration configuration, LinkedList filters, boolean inArrayContext) { + public Object filter(Object obj, Object root, Configuration configuration, LinkedList filters, boolean inArrayContext) { + String condition = getCondition(root); + JsonProvider jsonProvider = configuration.getProvider(); if (jsonProvider.isArray(obj)) { - if (!inArrayContext) { + if (isInt(condition)) { + return new ArrayIndexFilter("[" + condition + "]").filter(obj, root, configuration, filters, inArrayContext); + } else if (!inArrayContext) { throw new PathNotFoundException("Path '" + condition + "' is being applied to an array. Arrays can not have attributes."); } else { Object result = jsonProvider.createArray(); @@ -111,20 +112,24 @@ public class FieldFilter extends PathTokenFilter { } } - @Override - public Object filter(Object obj, Configuration configuration) { + public Object filter(Object obj, Object root, Configuration configuration) { + String condition = getCondition(root); JsonProvider jsonProvider = configuration.getProvider(); if (jsonProvider.isArray(obj)) { - return obj; + if (isInt(condition)) { + return new ArrayIndexFilter("[" + condition + "]").filter(obj, root, configuration); + } else { + return obj; + } } else { return jsonProvider.getProperty(obj, condition); } } @Override - public Object getRef(Object obj, Configuration configuration) { - return filter(obj, configuration); + public Object getRef(Object obj, Object root, Configuration configuration) { + return filter(obj, root, configuration); } @Override @@ -132,5 +137,20 @@ public class FieldFilter extends PathTokenFilter { return false; } + public String getCondition(Object root) { + if (condition.startsWith("[$")) { + return JsonPath.read(root, trim(condition, 1, 1)).toString(); + } else { + return condition; + } + } + private boolean isInt(String str) { + try { + Integer.parseInt(str); + } catch (NumberFormatException e) { + return false; + } + return true; + } } diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/FilterFactory.java b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/FilterFactory.java index bfd90374..ecc5f777 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/FilterFactory.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/FilterFactory.java @@ -68,6 +68,8 @@ public class FilterFactory { } else { throw new InvalidPathException("Failed to create PathTokenFilter for path fragment: " + pathFragment); } + } else if (pathFragment.startsWith("[$")) { + return new FieldFilter(token); } else { //[0] //[0,1, ...] diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/HasFieldFilter.java b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/HasFieldFilter.java index 347e586c..33f6c557 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/HasFieldFilter.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/HasFieldFilter.java @@ -39,7 +39,7 @@ public class HasFieldFilter extends PathTokenFilter { } @Override - public Object filter(Object obj, Configuration configuration) { + public Object filter(Object obj, Object root, Configuration configuration) { JsonProvider jsonProvider = configuration.getProvider(); //[?(@.isbn)] @@ -58,7 +58,7 @@ public class HasFieldFilter extends PathTokenFilter { } @Override - public Object getRef(Object obj, Configuration configuration) { + public Object getRef(Object obj, Object root, Configuration configuration) { throw new UnsupportedOperationException(); } diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/PassthroughFilter.java b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/PassthroughFilter.java index 06eb88a2..62f8b088 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/PassthroughFilter.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/PassthroughFilter.java @@ -30,12 +30,12 @@ public class PassthroughFilter extends PathTokenFilter { } @Override - public Object filter(Object obj, Configuration configuration) { + public Object filter(Object obj, Object root, Configuration configuration) { return obj; } @Override - public Object getRef(Object obj, Configuration configuration) { + public Object getRef(Object obj, Object root, Configuration configuration) { return obj; } diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/PathTokenFilter.java b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/PathTokenFilter.java index 105b59d0..b01e5008 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/PathTokenFilter.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/PathTokenFilter.java @@ -46,13 +46,13 @@ public abstract class PathTokenFilter { return res; } - public Object filter(Object obj, Configuration configuration, LinkedList filters, boolean inArrayContext){ - return filter(obj, configuration); + public Object filter(Object obj, Object root, Configuration configuration, LinkedList filters, boolean inArrayContext){ + return filter(obj, root, configuration); } - public abstract Object filter(Object obj, Configuration configuration); + public abstract Object filter(Object obj, Object root, Configuration configuration); - public abstract Object getRef(Object obj, Configuration configuration); + public abstract Object getRef(Object obj, Object root, Configuration configuration); public abstract boolean isArrayFilter(); diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ScanFilter.java b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ScanFilter.java index 877d2060..3e2887bd 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ScanFilter.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/ScanFilter.java @@ -28,7 +28,7 @@ public class ScanFilter extends PathTokenFilter { } @Override - public Object filter(Object obj, Configuration configuration) { + public Object filter(Object obj, Object root, Configuration configuration) { JsonProvider jsonProvider = configuration.getProvider(); Object result = jsonProvider.createArray(); scan(obj, result, jsonProvider); @@ -42,7 +42,7 @@ public class ScanFilter extends PathTokenFilter { } @Override - public Object getRef(Object obj, Configuration configuration) { + public Object getRef(Object obj, Object root, Configuration configuration) { throw new UnsupportedOperationException(); } diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/WildcardFilter.java b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/WildcardFilter.java index 1c85c156..98929ae2 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/filter/WildcardFilter.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/filter/WildcardFilter.java @@ -27,7 +27,7 @@ public class WildcardFilter extends PathTokenFilter { } @Override - public Object filter(Object obj, Configuration configuration) { + public Object filter(Object obj, Object root, Configuration configuration) { JsonProvider jsonProvider = configuration.getProvider(); Object result = jsonProvider.createArray(); @@ -46,7 +46,7 @@ public class WildcardFilter extends PathTokenFilter { } @Override - public Object getRef(Object obj, Configuration configuration) { + public Object getRef(Object obj, Object root, Configuration configuration) { throw new UnsupportedOperationException(); } diff --git a/json-path/src/test/java/com/jayway/jsonpath/ArraySlicingTest.java b/json-path/src/test/java/com/jayway/jsonpath/ArraySlicingTest.java index f5ba86f3..f7256540 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/ArraySlicingTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/ArraySlicingTest.java @@ -38,7 +38,7 @@ public class ArraySlicingTest { @Test public void get_by_position(){ - int result = JsonPath.read(JSON_ARRAY, "$[3]"); + int result = JsonPath.read(JSON_ARRAY, "$[3]"); assertEquals(7, result); } @@ -76,13 +76,13 @@ public class ArraySlicingTest { @Test public void get_from_tail(){ - int result = JsonPath.read(JSON_ARRAY, "$[3:]"); + int result = JsonPath.read(JSON_ARRAY, "$[3:]"); assertEquals(8, result); } @Test public void get_from_tail_length(){ - int result = JsonPath.read(JSON_ARRAY, "$[(@.length -3)]"); + int result = JsonPath.read(JSON_ARRAY, "$[(@.length -3)]"); assertEquals(8, result); } @@ -91,5 +91,4 @@ public class ArraySlicingTest { List result = JsonPath.read(JSON_ARRAY, "$[0,1,2]"); assertThat(result, Matchers.contains(1,3,5)); } - } 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 ee2ae0f9..da2bc8e8 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/JsonPathTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/JsonPathTest.java @@ -57,6 +57,15 @@ public class JsonPathTest { " \"foo:bar\": \"fooBar\",\n" + " \"dot.notation\": \"new\",\n" + " \"dash-notation\": \"dashes\"\n" + + " },\n" + + " \"foo\": {\n" + + " \"bar\": \"red\",\n" + + " \"red\": [\"bar\"],\n" + + " \"category\": \"reference\",\n" + + " \"category_array\": [\"reference\"],\n" + + " \"bar_hash\": [{\"red\": \"bar\"}],\n" + + " \"one\": 1,\n" + + " \"price\": 10\n" + " }\n" + " }\n" + "}"; @@ -180,7 +189,7 @@ public class JsonPathTest { Map result = JsonPath.read(DOCUMENT, "$.store"); - assertEquals(2, result.values().size()); + assertEquals(3, result.values().size()); } @Test @@ -315,7 +324,17 @@ public class JsonPathTest { } - + @Test + public void access_array_by_variable() { + assertEquals(JsonPath.read(DOCUMENT, "$.store.foo[$.store.foo.bar_hash[?(@.red)].red[0]]"), "red"); + assertEquals(JsonPath.read(DOCUMENT, "$.store.foo[$.store.foo.red[0]]"), "red"); + assertEquals(JsonPath.read(DOCUMENT, "$.store.book[$.store.foo.one].author"), "Evelyn Waugh"); + + assertThat(JsonPath.>read(DOCUMENT, "$.store.foo[$.store.foo.bar]"), hasItems("bar")); + assertThat(JsonPath.>read(DOCUMENT, "$..book[?(@.category==$.store.foo.category_array[0])].title"), hasItems("Sayings of the Century")); + assertThat(JsonPath.>read(DOCUMENT, "$..book[?(@.category==$.store.foo.category)].title"), hasItems("Sayings of the Century")); + assertThat(JsonPath.>read(DOCUMENT, "$..book[?(@['display-price'] < $.store.foo.price)].title"), hasItems("Sayings of the Century", "Moby Dick")); + } }