Browse Source

New PathTokenizer

pull/3/head
kalle 13 years ago
parent
commit
15e9769ab2
  1. 5
      README
  2. 2
      json-path/src/main/java/com/jayway/jsonpath/JsonPath.java
  3. 5
      json-path/src/main/java/com/jayway/jsonpath/PathUtil.java
  4. 23
      json-path/src/main/java/com/jayway/jsonpath/reader/PathToken.java
  5. 210
      json-path/src/main/java/com/jayway/jsonpath/reader/PathTokenizer.java
  6. 100
      json-path/src/main/java/com/jayway/jsonpath/reader/filter/ArrayEvalFilter.java
  7. 52
      json-path/src/main/java/com/jayway/jsonpath/reader/filter/ArrayIndexFilter.java
  8. 30
      json-path/src/main/java/com/jayway/jsonpath/reader/filter/FieldFilter.java
  9. 77
      json-path/src/main/java/com/jayway/jsonpath/reader/filter/Filter.java
  10. 52
      json-path/src/main/java/com/jayway/jsonpath/reader/filter/FilterFactory.java
  11. 38
      json-path/src/main/java/com/jayway/jsonpath/reader/filter/HasFieldFilter.java
  12. 18
      json-path/src/main/java/com/jayway/jsonpath/reader/filter/PassThrewFilter.java
  13. 48
      json-path/src/main/java/com/jayway/jsonpath/reader/filter/ScanFilter.java
  14. 167
      json-path/src/test/java/com/jayway/jsonpath/PathTokenizerTest.java
  15. 5
      json-path/src/test/java/com/jayway/jsonpath/SplitPathFragmentsTest.java

5
README

@ -1,5 +1,4 @@
Java DSL for reading and testing JSON documents.
Santa Fe LET 50
COPY DEPENDENCIES : mvn clean compile dependency:copy-dependencies -DstripVersion
FONT LOGO : Santa Fe LET 50

2
json-path/src/main/java/com/jayway/jsonpath/JsonPath.java

@ -177,7 +177,7 @@ public class JsonPath {
}
private static Object parse(String json) throws java.text.ParseException {
public static Object parse(String json) throws java.text.ParseException {
try {
return JSON_PARSER.parse(json);
} catch (ParseException e) {

5
json-path/src/main/java/com/jayway/jsonpath/PathUtil.java

@ -104,17 +104,16 @@ public class PathUtil {
sb.append(c);
}
}
if (includeSopChar) {
assertValidPeek(pathQueue, false, stopChars);
sb.append(pathQueue.poll());
} else {
assertValidPeek(pathQueue, true, stopChars);
}
return unWrapProperty(sb);
return clean(sb);
}
private static String unWrapProperty(StringBuilder sb) {
private static String clean(StringBuilder sb) {
String src = sb.toString();

23
json-path/src/main/java/com/jayway/jsonpath/reader/PathToken.java

@ -0,0 +1,23 @@
package com.jayway.jsonpath.reader;
import com.jayway.jsonpath.reader.filter.Filter;
import com.jayway.jsonpath.reader.filter.FilterFactory;
/**
* Created by IntelliJ IDEA.
* User: kallestenflo
* Date: 11/4/11
* Time: 10:00 PM
*/
public class PathToken {
private Filter filter;
public PathToken(String pathFragment) {
filter = FilterFactory.createFilter(pathFragment);
}
public Object filter(Object model){
return filter.filter(model);
}
}

210
json-path/src/main/java/com/jayway/jsonpath/reader/PathTokenizer.java

@ -0,0 +1,210 @@
package com.jayway.jsonpath.reader;
import com.jayway.jsonpath.InvalidPathException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
/**
* Created by IntelliJ IDEA.
* User: kallestenflo
* Date: 11/4/11
* Time: 9:53 PM
*/
public class PathTokenizer implements Iterable<PathToken> {
private char[] pathChars;
private int index = 0;
private List<PathToken> pathTokens = new LinkedList<PathToken>();
public PathTokenizer(String jsonPath) {
if (!jsonPath.startsWith("$") && !jsonPath.startsWith("$[")) {
jsonPath = "$." + jsonPath;
}
pathChars = jsonPath.toCharArray();
for (String pathFragment : splitPath()) {
pathTokens.add(new PathToken(pathFragment));
}
}
public Iterator<PathToken> iterator() {
return pathTokens.iterator();
}
//--------------------------------------------
//
// Split path
//
//--------------------------------------------
private boolean isEmpty() {
return index == pathChars.length;
}
private char peek() {
return pathChars[index];
}
private char poll() {
char peek = peek();
index++;
return peek;
}
public List<String> splitPath() {
List<String> fragments = new LinkedList<String>();
while (!isEmpty()) {
skip(' ');
char current = peek();
switch (current) {
case '$':
fragments.add(Character.toString(current));
poll();
break;
case '.':
poll();
if (peek() == '.') {
poll();
fragments.add("..");
assertNotInvalidPeek('.');
}
break;
case '[':
fragments.add(extract(true, ']'));
break;
default:
fragments.add(extract(false, '[', '.'));
}
}
return fragments;
}
private String extract(boolean includeSopChar, char... stopChars) {
StringBuilder sb = new StringBuilder();
while (!isEmpty() && (!isStopChar(peek(), stopChars))) {
char c = poll();
if (isStopChar(c, stopChars)) {
if (includeSopChar) {
sb.append(c);
}
} else {
sb.append(c);
}
}
if (includeSopChar) {
assertValidPeek(false, stopChars);
sb.append(poll());
} else {
assertValidPeek(true, stopChars);
}
return clean(sb);
}
private String clean(StringBuilder sb) {
String src = sb.toString();
src = trim(src, "'");
src = trim(src, ")");
src = trim(src, "(");
src = trimLeft(src, "?");
src = trimLeft(src, "@");
if (src.length() > 5 && src.subSequence(0, 2).equals("['")) {
src = src.substring(2);
src = src.substring(0, src.length() - 2);
}
return src.trim();
}
private String trim(String src, String trim) {
return trimLeft(trimRight(src, trim), trim);
}
private String trimRight(String src, String trim) {
String scanFor = trim + " ";
if (src.contains(scanFor)) {
while (src.contains(scanFor)) {
src = src.replace(scanFor, trim);
}
}
return src;
}
private String trimLeft(String src, String trim) {
String scanFor = " " + trim;
if (src.contains(scanFor)) {
while (src.contains(scanFor)) {
src = src.replace(scanFor, trim);
}
}
return src;
}
private boolean isStopChar(char c, char... scanFor) {
boolean found = false;
for (char check : scanFor) {
if (check == c) {
found = true;
break;
}
}
return found;
}
private void skip(char target) {
if (isEmpty()) {
return;
}
while (pathChars[index] == target) {
index++;
}
}
private void assertNotInvalidPeek(char... invalidChars) {
if (isEmpty()) {
return;
}
char peek = peek();
for (char check : invalidChars) {
if (check == peek) {
throw new InvalidPathException("Char: " + peek + " at current position is not valid!");
}
}
}
private void assertValidPeek(boolean acceptEmpty, char... validChars) {
if (isEmpty() && acceptEmpty) {
return;
}
if (isEmpty()) {
throw new InvalidPathException("Path is incomplete");
}
boolean found = false;
char peek = peek();
for (char check : validChars) {
if (check == peek) {
found = true;
break;
}
}
if (!found) {
throw new InvalidPathException("Path is invalid");
}
}
}

100
json-path/src/main/java/com/jayway/jsonpath/reader/filter/ArrayEvalFilter.java

@ -0,0 +1,100 @@
package com.jayway.jsonpath.reader.filter;
import com.jayway.jsonpath.InvalidPathException;
import com.jayway.jsonpath.eval.ExpressionEvaluator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Created by IntelliJ IDEA.
* User: kallestenflo
* Date: 11/5/11
* Time: 12:35 AM
*/
public class ArrayEvalFilter extends Filter {
public static final Pattern PATTERN = Pattern.compile("(.*?)\\s?([=<>]+)\\s?(.*)");
public ArrayEvalFilter(String condition) {
super(condition);
}
@Override
public Object filter(Object obj) {
//[?(@.isbn = 10)]
List<Object> src = toList(obj);
List<Object> result = new LinkedList<Object>();
String trimmedCondition = trim(condition, 5, 2);
ConditionStatement conditionStatement = createConditionStatement(trimmedCondition);
for (Object item : src) {
if (isMatch(item, conditionStatement)) {
result.add(item);
}
}
return result;
}
private boolean isMatch(Object check, ConditionStatement conditionStatement) {
if (!isMap(check)) {
return false;
}
Map obj = toMap(check);
if (!obj.containsKey(conditionStatement.getField())) {
return false;
}
Object propertyValue = obj.get(conditionStatement.getField());
if (isContainer(propertyValue)) {
return false;
}
return ExpressionEvaluator.eval(propertyValue, conditionStatement.getOperator(), conditionStatement.getExpected());
}
private 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);
return new ConditionStatement(property, operator, expected);
} else {
throw new InvalidPathException("Invalid match " + str);
}
}
private class ConditionStatement {
private String field;
private String operator;
private String expected;
private ConditionStatement(String field, String operator, String expected) {
this.field = field;
this.operator = operator;
this.expected = expected;
}
public String getField() {
return field;
}
public String getOperator() {
return operator;
}
public String getExpected() {
return expected;
}
}
}

52
json-path/src/main/java/com/jayway/jsonpath/reader/filter/ArrayIndexFilter.java

@ -0,0 +1,52 @@
package com.jayway.jsonpath.reader.filter;
import java.util.LinkedList;
import java.util.List;
/**
* Created by IntelliJ IDEA.
* User: kallestenflo
* Date: 11/4/11
* Time: 11:25 PM
*/
public class ArrayIndexFilter extends Filter {
public ArrayIndexFilter(String condition) {
super(condition);
}
@Override
public Object filter(Object obj) {
List<Object> src = toList(obj);
List<Object> result = new LinkedList<Object>();
String trimmedCondition = trim(condition, 1, 1);
if (trimmedCondition.startsWith(":")) {
trimmedCondition = trim(trimmedCondition, 1, 0);
int get = Integer.parseInt(trimmedCondition);
for (int i = 0; i < get; i++) {
result.add(src.get(i));
}
return result;
} else if (trimmedCondition.endsWith(":")) {
trimmedCondition = trim(trimmedCondition, 1, 1);
int get = Integer.parseInt(trimmedCondition);
return src.get(src.size() - get);
} else {
String[] indexArr = trimmedCondition.split(",");
if (indexArr.length == 1) {
return src.get(Integer.parseInt(indexArr[0]));
} else {
for (String idx : indexArr) {
result.add(src.get(Integer.parseInt(idx.trim())));
}
return result;
}
}
}
}

30
json-path/src/main/java/com/jayway/jsonpath/reader/filter/FieldFilter.java

@ -0,0 +1,30 @@
package com.jayway.jsonpath.reader.filter;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* Created by IntelliJ IDEA.
* User: kallestenflo
* Date: 11/4/11
* Time: 10:17 PM
*/
public class FieldFilter extends Filter {
public FieldFilter(String condition) {
super(condition);
}
public Object filter(Object obj) {
if(isList(obj)){
List<Object> result = new LinkedList<Object>();
for (Object item : toList(obj)) {
result.add(filter(item));
}
return result;
} else {
return getMapValue(obj, condition);
}
}
}

77
json-path/src/main/java/com/jayway/jsonpath/reader/filter/Filter.java

@ -0,0 +1,77 @@
package com.jayway.jsonpath.reader.filter;
import java.util.List;
import java.util.Map;
/**
* Created by IntelliJ IDEA.
* User: kallestenflo
* Date: 11/4/11
* Time: 10:14 PM
*/
public abstract class Filter {
protected final String condition;
public Filter(String condition) {
this.condition = condition;
}
/**
* checks if object is <code>instanceof</code> <code>java.util.List</code> or <code>java.util.Map</code>
*
* @param obj object to check
* @return true if List or Map
*/
protected boolean isContainer(Object obj) {
return (isList(obj) || isMap(obj));
}
/**
* checks if object is <code>instanceof</code> <code>java.util.List</code>
*
* @param obj object to check
* @return true if List
*/
protected boolean isList(Object obj) {
return (obj instanceof List);
}
protected List<Object> toList(Object list) {
return (List<Object>) list;
}
protected Map<String, Object> toMap(Object map) {
return (Map<String, Object>) map;
}
protected Object getMapValue(Object obj, String key) {
Map<String, Object> map = toMap(obj);
return map.get(key);
}
/**
* checks if object is <code>instanceof</code> <code>java.util.Map</code>
*
* @param obj object to check
* @return true if Map
*/
protected boolean isMap(Object obj) {
return (obj instanceof Map);
}
protected String trim(String str, int front, int end) {
String res = str;
if (front > 0) {
res = str.substring(front);
}
if (end > 0) {
res = res.substring(0, res.length() - end);
}
return res;
}
public abstract Object filter(Object obj);
}

52
json-path/src/main/java/com/jayway/jsonpath/reader/filter/FilterFactory.java

@ -0,0 +1,52 @@
package com.jayway.jsonpath.reader.filter;
import com.jayway.jsonpath.filter.ListEvalFilter;
/**
* Created by IntelliJ IDEA.
* User: kallestenflo
* Date: 11/4/11
* Time: 10:13 PM
*/
public class FilterFactory {
public static Filter createFilter(String pathFragment) {
if ("$".equals(pathFragment) || "*".equals(pathFragment) || "[*]".equals(pathFragment)) {
return new PassThrewFilter(pathFragment);
} else if (pathFragment.contains("..")) {
return new ScanFilter(pathFragment);
} else if (!pathFragment.contains("[")) {
return new FieldFilter(pathFragment);
} else if (pathFragment.contains("[")) {
if (pathFragment.startsWith("[?")) {
if(!pathFragment.contains("=")){
//[?(@.isbn)]
return new HasFieldFilter(pathFragment);
} else {
//[?(@.name='foo')]
return new ArrayEvalFilter(pathFragment);
}
} else {
//[0]
//[0,1, ...]
//[-1:]
//[:1]
return new ArrayIndexFilter(pathFragment);
}
}
throw new UnsupportedOperationException("..");
}
}

38
json-path/src/main/java/com/jayway/jsonpath/reader/filter/HasFieldFilter.java

@ -0,0 +1,38 @@
package com.jayway.jsonpath.reader.filter;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* Created by IntelliJ IDEA.
* User: kallestenflo
* Date: 11/5/11
* Time: 12:17 AM
*/
public class HasFieldFilter extends Filter {
public HasFieldFilter(String condition) {
super(condition);
}
@Override
public Object filter(Object obj) {
//[?(@.isbn)]
List<Object> src = toList(obj);
List<Object> result = new LinkedList<Object>();
String trimmedCondition = trim(condition, 5, 2);
for (Object item : src) {
if(isMap(item)){
Map<String, Object> map = toMap(item);
if(map.containsKey(trimmedCondition)){
result.add(map);
}
}
}
return result;
}
}

18
json-path/src/main/java/com/jayway/jsonpath/reader/filter/PassThrewFilter.java

@ -0,0 +1,18 @@
package com.jayway.jsonpath.reader.filter;
/**
* Created by IntelliJ IDEA.
* User: kallestenflo
* Date: 11/4/11
* Time: 10:15 PM
*/
public class PassThrewFilter extends Filter {
public PassThrewFilter(String condition) {
super(condition);
}
public Object filter(Object obj) {
return obj;
}
}

48
json-path/src/main/java/com/jayway/jsonpath/reader/filter/ScanFilter.java

@ -0,0 +1,48 @@
package com.jayway.jsonpath.reader.filter;
import com.jayway.jsonpath.JsonUtil;
import java.util.LinkedList;
import java.util.List;
/**
* Created by IntelliJ IDEA.
* User: kallestenflo
* Date: 11/7/11
* Time: 12:31 PM
*/
public class ScanFilter extends Filter {
public ScanFilter(String condition) {
super(condition);
}
@Override
public Object filter(Object obj) {
List<Object> result = new LinkedList<Object>();
scan(obj, result);
return result;
}
private void scan(Object container, List<Object> result) {
if (isMap(container)) {
result.add(container);
for (Object value : toMap(container).values()) {
if (isContainer(value)) {
scan(value, result);
}
}
} else if (isList(container)) {
for (Object value : JsonUtil.toList(container)) {
if (isContainer(value)) {
scan(value, result);
}
}
}
}
}

167
json-path/src/test/java/com/jayway/jsonpath/PathTokenizerTest.java

@ -0,0 +1,167 @@
package com.jayway.jsonpath;
import com.jayway.jsonpath.reader.PathToken;
import com.jayway.jsonpath.reader.PathTokenizer;
import org.junit.Test;
import java.util.List;
import static junit.framework.Assert.assertEquals;
import static org.hamcrest.Matchers.hasItems;
import static org.junit.Assert.assertThat;
/**
* Created by IntelliJ IDEA.
* User: kallestenflo
* Date: 11/4/11
* Time: 10:44 PM
*/
public class PathTokenizerTest {
public final static String DOCUMENT =
"{ \"store\": {\n" +
" \"book\": [ \n" +
" { \"category\": \"reference\",\n" +
" \"author\": \"Nigel Rees\",\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" +
" },\n" +
" { \"category\": \"fiction\",\n" +
" \"author\": \"Herman Melville\",\n" +
" \"title\": \"Moby Dick\",\n" +
" \"isbn\": \"0-553-21311-3\",\n" +
" \"price\": 8.99\n" +
" },\n" +
" { \"category\": \"fiction\",\n" +
" \"author\": \"J. R. R. Tolkien\",\n" +
" \"title\": \"The Lord of the Rings\",\n" +
" \"custom\": \"onely this\",\n" +
" \"isbn\": \"0-395-19395-8\",\n" +
" \"price\": 22.99\n" +
" }\n" +
" ],\n" +
" \"bicycle\": {\n" +
" \"color\": \"red\",\n" +
" \"price\": 19.95,\n" +
" \"foo:bar\": \"fooBar\",\n" +
" \"dot.notation\": \"new\"\n" +
" }\n" +
" }\n" +
"}";
@Test
public void path_tokens_can_be_read() throws Exception {
Object result = JsonPath.parse(DOCUMENT);
for (PathToken pathToken : new PathTokenizer("$.store.bicycle.color")) {
result = pathToken.filter(result);
}
assertEquals("red", result);
}
@Test
public void read_an_array_without_filters() throws Exception {
Object result = JsonPath.parse(DOCUMENT);
for (PathToken pathToken : new PathTokenizer("$.store.book")) {
result = pathToken.filter(result);
}
assertEquals(4, toList(result).size());
}
@Test
public void read_a_literal_property_from_object_in_array() throws Exception {
Object result = JsonPath.parse(DOCUMENT);
for (PathToken pathToken : new PathTokenizer("$.store.book[*].title")) {
result = pathToken.filter(result);
}
assertEquals(4, toList(result).size());
}
@Test
public void read_a_literal_property_from_position_in_array() throws Exception {
Object result = JsonPath.parse(DOCUMENT);
for (PathToken pathToken : new PathTokenizer("$.store.book[0].title")) {
result = pathToken.filter(result);
}
assertEquals("Sayings of the Century", result);
}
@Test
public void read_a_literal_property_from_two_positions_in_array() throws Exception {
Object result = JsonPath.parse(DOCUMENT);
for (PathToken pathToken : new PathTokenizer("$.store.book[0, 1].author")) {
result = pathToken.filter(result);
}
assertThat(this.<String>toList(result), hasItems("Nigel Rees", "Evelyn Waugh"));
}
@Test
public void read_a_literal_property_from_head_in_array() throws Exception {
Object result = JsonPath.parse(DOCUMENT);
for (PathToken pathToken : new PathTokenizer("$.store.book[:2].author")) {
result = pathToken.filter(result);
}
assertThat(this.<String>toList(result), hasItems("Nigel Rees", "Evelyn Waugh"));
}
@Test
public void read_a_literal_property_from_tail_in_array() throws Exception {
Object result = JsonPath.parse(DOCUMENT);
for (PathToken pathToken : new PathTokenizer("$.store.book[-1:].author")) {
result = pathToken.filter(result);
}
assertEquals("J. R. R. Tolkien", result);
}
@Test
public void field_defined_in_array_object() throws Exception {
Object result = JsonPath.parse(DOCUMENT);
for (PathToken pathToken : new PathTokenizer("$.store.book[?(@.custom)].author")) {
result = pathToken.filter(result);
}
assertThat(this.<String>toList(result), hasItems("J. R. R. Tolkien"));
}
@Test
public void property_value_in_array_object() throws Exception {
Object result = JsonPath.parse(DOCUMENT);
for (PathToken pathToken : new PathTokenizer("$.store.book[?(@.custom = 'onely this')].author")) {
result = pathToken.filter(result);
}
assertThat(this.<String>toList(result), hasItems("J. R. R. Tolkien"));
}
@Test
public void deep_scan() throws Exception {
Object result = JsonPath.parse(DOCUMENT);
for (PathToken pathToken : new PathTokenizer("$..author")) {
result = pathToken.filter(result);
}
assertThat(this.<String>toList(result), hasItems("Nigel Rees","Evelyn Waugh", "J. R. R. Tolkien"));
}
private <T> List<T> toList(Object obj) {
return (List<T>) obj;
}
}

5
json-path/src/test/java/com/jayway/jsonpath/SplitPathFragmentsTest.java

@ -67,6 +67,8 @@ public class SplitPathFragmentsTest {
assertPath("$.[ 'store' ]", hasItems("$", "store"));
assertPath("$.['store bore']", hasItems("$", "store bore"));
assertPath("$..book[ ?(@.price<10) ]", hasItems("$", "..", "book", "[?(@.price<10)]"));
assertPath("$..book[?(@.price<10 )]", hasItems("$", "..", "book", "[?(@.price<10)]"));
@ -97,10 +99,13 @@ public class SplitPathFragmentsTest {
private void assertPath(String path, Matcher<Iterable<String>> matcher) {
System.out.println("PATH: " + path);
List<String> fragments = PathUtil.splitPath(path);
for (String fragment : fragments) {
System.out.println(fragment);
}
assertThat(fragments, matcher);
System.out.println("----------------------------------");
}

Loading…
Cancel
Save