Browse Source

restructured JsonPath impl

pull/1/merge
kalle 14 years ago
parent
commit
bd327d8ded
  1. 5
      json-assert/src/main/java/com/jayway/jsonassert/JsonPath.java
  2. 83
      json-assert/src/main/java/com/jayway/jsonpath/JsonPath.java
  3. 32
      json-assert/src/main/java/com/jayway/jsonpath/PathItem.java
  4. 60
      json-assert/src/main/java/com/jayway/jsonpath/PathUtil.java
  5. 24
      json-assert/src/main/java/com/jayway/jsonpath/filter/JsonPathFilterBase.java
  6. 47
      json-assert/src/main/java/com/jayway/jsonpath/filter/JsonPathFilterChain.java
  7. 37
      json-assert/src/main/java/com/jayway/jsonpath/filter/JsonPathFilterFactory.java
  8. 177
      json-assert/src/main/java/com/jayway/jsonpath/filter/ListFilter.java
  9. 41
      json-assert/src/main/java/com/jayway/jsonpath/filter/PropertyFilter.java
  10. 17
      json-assert/src/main/java/com/jayway/jsonpath/filter/RootFilter.java
  11. 50
      json-assert/src/main/java/com/jayway/jsonpath/filter/TraverseFilter.java
  12. 150
      json-assert/src/test/java/com/jayway/jsonpath/JsonPathTest.java
  13. 68
      json-assert/src/test/java/com/jayway/jsonpath/SplitPathFragmentsTest.java

5
json-assert/src/main/java/com/jayway/jsonassert/JsonPath.java

@ -10,6 +10,11 @@ import java.util.Map;
*/
public interface JsonPath {
public enum ResultType {
PATH,
JSON
}
/**
* Get a new reader with the given path as root
*

83
json-assert/src/main/java/com/jayway/jsonpath/JsonPath.java

@ -0,0 +1,83 @@
package com.jayway.jsonpath;
import com.jayway.jsonpath.filter.JsonPathFilterChain;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import java.util.List;
import static java.lang.String.format;
/**
* Created by IntelliJ IDEA.
* User: kallestenflo
* Date: 2/2/11
* Time: 1:03 PM
*/
public class JsonPath {
private static final JSONParser JSON_PARSER = new JSONParser();
private JsonPathFilterChain filters;
public static JsonPath compile(String jsonPath) {
return new JsonPath(jsonPath);
}
private JsonPath(String jsonPath) {
this.filters = new JsonPathFilterChain(PathUtil.splitPath(jsonPath));
}
public <T> List<T> read(Object json) {
return (List<T>) filters.filter(json);
}
public <T> List<T> read(String json) throws java.text.ParseException {
Object root = null;
try {
root = JSON_PARSER.parse(json);
} catch (ParseException e) {
throw new java.text.ParseException(json, e.getPosition());
}
return (List<T>) filters.filter(root);
}
public static <T> List<T> read(String json, String jsonPath) throws java.text.ParseException {
JsonPath path = compile(jsonPath);
return path.read(json);
}
public static <T> List<T> read(Object json, String jsonPath) throws java.text.ParseException {
JsonPath path = compile(jsonPath);
return path.read(json);
}
public static <T> T readOne(String json, String jsonPath) throws java.text.ParseException {
JsonPath path = compile(jsonPath);
List<Object> result = read(json, jsonPath);
if (result.size() != 1) {
throw new RuntimeException(format("Expected one result when reading path: %s but was: ", jsonPath, result.size()));
}
return (T) result.get(0);
}
public static <T> T readOne(Object json, String jsonPath) throws java.text.ParseException {
JsonPath path = compile(jsonPath);
List<Object> result = read(json, jsonPath);
if (result.size() != 1) {
throw new RuntimeException(format("Expected one result when reading path: %s but was: ", jsonPath, result.size()));
}
return (T) result.get(0);
}
}

32
json-assert/src/main/java/com/jayway/jsonpath/PathItem.java

@ -0,0 +1,32 @@
package com.jayway.jsonpath;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
/**
* Created by IntelliJ IDEA.
* User: kallestenflo
* Date: 2/2/11
* Time: 2:33 PM
*/
public class PathItem {
private final String path;
private final Object target;
public PathItem(String path, Object target) {
this.path = path;
this.target = target;
}
public String getPath() {
return path;
}
public Object getTarget() {
return target;
}
}

60
json-assert/src/main/java/com/jayway/jsonpath/PathUtil.java

@ -0,0 +1,60 @@
package com.jayway.jsonpath;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import java.util.LinkedList;
import java.util.List;
/**
* Created by IntelliJ IDEA.
* User: kallestenflo
* Date: 2/2/11
* Time: 2:08 PM
*/
public class PathUtil {
public static boolean isContainer(Object obj) {
return (isArray(obj) || isDocument(obj));
}
public static boolean isArray(Object obj) {
return (obj instanceof JSONArray);
}
public static boolean isDocument(Object obj) {
return (obj instanceof JSONObject);
}
public static JSONArray toArray(Object array) {
return (JSONArray) array;
}
public static JSONObject toDocument(Object document) {
return (JSONObject) document;
}
public static List<String> splitPath(String jsonPath) {
LinkedList<String> fragments = new LinkedList<String>();
if (!jsonPath.startsWith("$.")) {
jsonPath = "$." + jsonPath;
}
jsonPath = jsonPath.replace("..", ".~.")
.replace("[", ".[")
.replace("@.", "@");
String[] split = jsonPath.split("\\.");
for (int i = 0; i < split.length; i++) {
if(split[i].trim().isEmpty()){
continue;
}
fragments.add(split[i].replace("@", "@.").replace("~", ".."));
}
return fragments;
}
}

24
json-assert/src/main/java/com/jayway/jsonpath/filter/JsonPathFilterBase.java

@ -0,0 +1,24 @@
package com.jayway.jsonpath.filter;
import com.jayway.jsonpath.PathItem;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import java.util.Collection;
import java.util.List;
/**
* Created by IntelliJ IDEA.
* User: kallestenflo
* Date: 2/2/11
* Time: 2:01 PM
*/
public abstract class JsonPathFilterBase {
public abstract List<Object> apply(List<Object> filter);
}

47
json-assert/src/main/java/com/jayway/jsonpath/filter/JsonPathFilterChain.java

@ -0,0 +1,47 @@
package com.jayway.jsonpath.filter;
import org.json.simple.JSONArray;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
/**
* User: kallestenflo
* Date: 2/2/11
* Time: 2:00 PM
*/
public class JsonPathFilterChain {
private static final List<Object> EMPTY_LIST = Collections.unmodifiableList(new JSONArray());
private List<JsonPathFilterBase> filters;
public JsonPathFilterChain(List<String> pathFragments) {
filters = configureFilters(pathFragments);
}
private List<JsonPathFilterBase> configureFilters(List<String> pathFragments) {
List<JsonPathFilterBase> configured = new LinkedList<JsonPathFilterBase>();
for (String pathFragment : pathFragments) {
configured.add(JsonPathFilterFactory.createFilter(pathFragment));
}
return configured;
}
public List<Object> filter(Object root) {
List<Object> rootList = new JSONArray();
rootList.add(root);
List<Object> result = rootList;
for (JsonPathFilterBase filter : filters) {
result = filter.apply(result);
}
return result;
}
}

37
json-assert/src/main/java/com/jayway/jsonpath/filter/JsonPathFilterFactory.java

@ -0,0 +1,37 @@
package com.jayway.jsonpath.filter;
import java.util.regex.Pattern;
/**
* User: kallestenflo
* Date: 2/2/11
* Time: 2:03 PM
*/
public class JsonPathFilterFactory {
private static final Pattern ROOT_FILTER = Pattern.compile("\\$");
private static final Pattern PROPERTY_FILTER = Pattern.compile("\\w+");
private static final Pattern WILDCARD_PROPERTY_FILTER = Pattern.compile("\\*");
private static final Pattern LIST_FILTER = Pattern.compile("\\[.*?\\]");
private static final Pattern TRAVERSE_FILTER = Pattern.compile("\\.\\.");
public static JsonPathFilterBase createFilter(String pathFragment){
if(ROOT_FILTER.matcher(pathFragment).matches()){
return new RootFilter();
}
else if(PROPERTY_FILTER.matcher(pathFragment).matches() || WILDCARD_PROPERTY_FILTER.matcher(pathFragment).matches() ){
return new PropertyFilter(pathFragment);
}
else if(LIST_FILTER.matcher(pathFragment).matches()){
return new ListFilter(pathFragment);
}
else if(TRAVERSE_FILTER.matcher(pathFragment).matches()){
return new TraverseFilter(pathFragment);
}
return null;
}
}

177
json-assert/src/main/java/com/jayway/jsonpath/filter/ListFilter.java

@ -0,0 +1,177 @@
package com.jayway.jsonpath.filter;
import com.jayway.jsonpath.PathUtil;
import org.json.simple.JSONArray;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Created by IntelliJ IDEA.
* User: kallestenflo
* Date: 2/2/11
* Time: 2:32 PM
*/
public class ListFilter extends JsonPathFilterBase {
private static final Pattern LIST_INDEX_PATTERN = Pattern.compile("\\[(\\s?\\d+\\s?,?)+\\]");
private static final Pattern LIST_PULL_PATTERN = Pattern.compile("\\[\\s?:(\\d+)\\s?\\]"); //[ :2 ]
private static final Pattern LIST_WILDCARD_PATTERN = Pattern.compile("\\[\\*\\]");
private static final Pattern LIST_TAIL_PATTERN_SHORT = Pattern.compile("\\[\\s*-\\s*(\\d+):\\s*\\]"); // [(@.length - 12)] OR [-13:]
private static final Pattern LIST_TAIL_PATTERN_LONG = Pattern.compile("\\[\\s*\\(\\s*@\\.length\\s*-\\s*(\\d+)\\s*\\)\\s*\\]");
private static final Pattern LIST_TAIL_PATTERN = Pattern.compile("(" + LIST_TAIL_PATTERN_SHORT.pattern() + "|" + LIST_TAIL_PATTERN_LONG.pattern() + ")");
private static final Pattern LIST_ITEM_HAS_PROPERTY_PATTERN = Pattern.compile("\\[\\s?\\?\\s?\\(\\s?@\\.(\\w+)\\s?\\)\\s?\\]");
private final String pathFragment;
public ListFilter(String pathFragment) {
this.pathFragment = pathFragment;
}
@Override
public List<Object> apply(List<Object> items) {
List<Object> result = new JSONArray();
if (LIST_INDEX_PATTERN.matcher(pathFragment).matches()) {
return filterByListIndex(items);
} else if (LIST_WILDCARD_PATTERN.matcher(pathFragment).matches()) {
return filterByWildcard(items);
} else if (LIST_TAIL_PATTERN.matcher(pathFragment).matches()) {
return filterByListTailIndex(items);
} else if (LIST_PULL_PATTERN.matcher(pathFragment).matches()) {
return filterByPullIndex(items);
} else if (LIST_ITEM_HAS_PROPERTY_PATTERN.matcher(pathFragment).matches()){
return filterByItemProperty(items);
}
return result;
}
private List<Object> filterByItemProperty(List<Object> items) {
List<Object> result = new JSONArray();
String prop = getFilterProperty();
for (Object current : items) {
for (Object item : PathUtil.toArray(current)) {
if(PathUtil.isDocument(item)){
if(PathUtil.toDocument(item).containsKey(prop)) {
result.add(item);
}
}
}
}
return result;
}
private List<Object> filterByWildcard(List<Object> items) {
List<Object> result = new JSONArray();
for (Object current : items) {
result.addAll(PathUtil.toArray(current));
}
return result;
}
private List<Object> filterByListTailIndex(List<Object> items) {
List<Object> result = new JSONArray();
for (Object current : items) {
List array = PathUtil.toArray(current);
result.add(array.get(getTailIndex(array.size())));
}
return result;
}
private List<Object> filterByListIndex(List<Object> items) {
List<Object> result = new JSONArray();
for (Object current : items) {
Integer[] index = getArrayIndex();
for (int i : index) {
result.add(PathUtil.toArray(current).get(i));
}
}
return result;
}
private List<Object> filterByPullIndex(List<Object> items) {
List<Object> result = new JSONArray();
for (Object current : items) {
Integer[] index = getListPullIndex();
for (int i : index) {
result.add(PathUtil.toArray(current).get(i));
}
}
return result;
}
private String getFilterProperty(){
Matcher matcher = LIST_ITEM_HAS_PROPERTY_PATTERN.matcher(pathFragment);
if (matcher.matches()) {
return matcher.group(1);
}
throw new IllegalArgumentException("invalid list filter property");
}
private int getTailIndex(int arraySize) {
Matcher matcher = LIST_TAIL_PATTERN_SHORT.matcher(pathFragment);
if (matcher.matches()) {
int index = Integer.parseInt(matcher.group(1));
return arraySize - index;
}
matcher = LIST_TAIL_PATTERN_LONG.matcher(pathFragment);
if (matcher.matches()) {
int index = Integer.parseInt(matcher.group(1));
return arraySize - index;
}
throw new IllegalArgumentException("invalid list index");
}
private Integer[] getListPullIndex() {
Matcher matcher = LIST_PULL_PATTERN.matcher(pathFragment);
if (matcher.matches()) {
int pullCount = Integer.parseInt(matcher.group(1));
List<Integer> result = new LinkedList<Integer>();
for (int y = 0; y < pullCount; y++) {
result.add(y);
}
return result.toArray(new Integer[0]);
}
throw new IllegalArgumentException("invalid list index");
}
private Integer[] getArrayIndex() {
String prepared = pathFragment.replaceAll(" ", "");
prepared = prepared.substring(1, prepared.length() - 1);
List<Integer> index = new LinkedList<Integer>();
String[] split = prepared.split(",");
for (String s : split) {
index.add(Integer.parseInt(s));
}
return index.toArray(new Integer[0]);
}
}

41
json-assert/src/main/java/com/jayway/jsonpath/filter/PropertyFilter.java

@ -0,0 +1,41 @@
package com.jayway.jsonpath.filter;
import com.jayway.jsonpath.PathUtil;
import org.json.simple.JSONArray;
import java.util.List;
/**
* Created by IntelliJ IDEA.
* User: kallestenflo
* Date: 2/2/11
* Time: 2:32 PM
*/
public class PropertyFilter extends JsonPathFilterBase {
private final String pathFragment;
public PropertyFilter(String pathFragment) {
this.pathFragment = pathFragment;
}
@Override
public List<Object> apply(List<Object> filter) {
List<Object> result = new JSONArray();
for (Object current : filter) {
if ("*".equals(pathFragment)) {
for (Object value : PathUtil.toDocument(current).values()) {
result.add(value);
}
} else {
if (PathUtil.toDocument(current).containsKey(pathFragment)) {
result.add(PathUtil.toDocument(current).get(pathFragment));
}
}
}
return result;
}
}

17
json-assert/src/main/java/com/jayway/jsonpath/filter/RootFilter.java

@ -0,0 +1,17 @@
package com.jayway.jsonpath.filter;
import java.util.List;
/**
* Created by IntelliJ IDEA.
* User: kallestenflo
* Date: 2/2/11
* Time: 2:31 PM
*/
public class RootFilter extends JsonPathFilterBase{
@Override
public List<Object> apply(List<Object> filter) {
return filter;
}
}

50
json-assert/src/main/java/com/jayway/jsonpath/filter/TraverseFilter.java

@ -0,0 +1,50 @@
package com.jayway.jsonpath.filter;
import com.jayway.jsonpath.PathUtil;
import org.json.simple.JSONArray;
import java.util.List;
/**
* Created by IntelliJ IDEA.
* User: kallestenflo
* Date: 2/2/11
* Time: 2:33 PM
*/
public class TraverseFilter extends JsonPathFilterBase {
private final String pathFragment;
public TraverseFilter(String pathFragment) {
this.pathFragment = pathFragment;
}
@Override
public List<Object> apply(List<Object> filter) {
List<Object> result = new JSONArray();
traverse(filter, result);
return result;
}
private void traverse(Object container, List<Object> result) {
if (PathUtil.isDocument(container)) {
result.add(container);
for (Object value : PathUtil.toDocument(container).values()) {
if (PathUtil.isContainer(value)) {
traverse(value, result);
}
}
} else if (PathUtil.isArray(container)) {
for (Object value : PathUtil.toArray(container)) {
if (PathUtil.isContainer(value)) {
traverse(value, result);
}
}
}
}
}

150
json-assert/src/test/java/com/jayway/jsonpath/JsonPathTest.java

@ -0,0 +1,150 @@
package com.jayway.jsonpath;
import org.apache.commons.lang.StringUtils;
import org.junit.Test;
import java.util.List;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItems;
import static org.junit.Assert.*;
/**
* Created by IntelliJ IDEA.
* User: kallestenflo
* Date: 2/2/11
* Time: 3:07 PM
*/
public class JsonPathTest {
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" +
" \"isbn\": \"0-395-19395-8\",\n" +
" \"price\": 22.99\n" +
" }\n" +
" ],\n" +
" \"bicycle\": {\n" +
" \"color\": \"red\",\n" +
" \"price\": 19.95\n" +
" }\n" +
" }\n" +
"}";
@Test
public void read_document_from_root() throws Exception {
JsonPath path = JsonPath.compile("$.store");
List<Object> list = path.read(DOCUMENT);
System.out.println(list.toString());
}
@Test
public void read_store_book_1() throws Exception {
JsonPath path = JsonPath.compile("$.store.book[1]");
List<Object> list = path.read(DOCUMENT);
System.out.println(list.toString());
}
@Test
public void read_store_book_wildcard() throws Exception {
JsonPath path = JsonPath.compile("$.store.book[*]");
List<Object> list = path.read(DOCUMENT);
System.out.println(list.toString());
}
@Test
public void read_store_book_author() throws Exception {
assertThat(JsonPath.<String>read(DOCUMENT, "$.store.book[*].author"), hasItems("Nigel Rees", "Evelyn Waugh", "Herman Melville", "J. R. R. Tolkien"));
}
@Test
public void all_authors() throws Exception {
assertThat(JsonPath.<String>read(DOCUMENT, "$..author"), hasItems("Nigel Rees", "Evelyn Waugh", "Herman Melville", "J. R. R. Tolkien"));
}
@Test
public void all_store_properties() throws Exception {
List<Object> itemsInStore = JsonPath.read(DOCUMENT, "$.store.*");
assertEquals(JsonPath.readOne(itemsInStore, "$.[0].[0].author"), "Nigel Rees");
assertEquals(JsonPath.readOne(itemsInStore, "$.[0][0].author"), "Nigel Rees");
}
@Test
public void all_prices_in_store() throws Exception {
assertThat(JsonPath.<Double>read(DOCUMENT, "$.store..price"), hasItems(8.95D, 12.99D, 8.99D, 19.95D));
}
@Test
public void access_array_by_index_from_tail() throws Exception {
assertThat(JsonPath.<String>readOne(DOCUMENT, "$..book[(@.length-1)].author"), equalTo("J. R. R. Tolkien"));
assertThat(JsonPath.<String>readOne(DOCUMENT, "$..book[-1:].author"), equalTo("J. R. R. Tolkien"));
}
@Test
public void read_store_book_index_0_and_1() throws Exception {
assertThat(JsonPath.<String>read(DOCUMENT, "$.store.book[0,1].author"), hasItems("Nigel Rees", "Evelyn Waugh"));
assertTrue(JsonPath.<String>read(DOCUMENT, "$.store.book[0,1].author").size() == 2);
}
@Test
public void read_store_book_pull_first_2() throws Exception {
assertThat(JsonPath.<String>read(DOCUMENT, "$.store.book[:2].author"), hasItems("Nigel Rees", "Evelyn Waugh"));
assertTrue(JsonPath.<String>read(DOCUMENT, "$.store.book[:2].author").size() == 2);
}
@Test
public void read_store_book_filter_by_isbn() throws Exception {
assertThat(JsonPath.<String>read(DOCUMENT, "$.store.book[?(@.isbn)].isbn"), hasItems("0-553-21311-3", "0-395-19395-8"));
assertTrue(JsonPath.<String>read(DOCUMENT, "$.store.book[?(@.isbn)].isbn").size() == 2);
}
@Test
public void all_members_of_all_documents() throws Exception {
List<String> all = JsonPath.read(DOCUMENT, "$..*");
System.out.println(StringUtils.join(all, "\n"));
System.out.println(all.toString());
}
}

68
json-assert/src/test/java/com/jayway/jsonpath/SplitPathFragmentsTest.java

@ -0,0 +1,68 @@
package com.jayway.jsonpath;
import org.apache.commons.lang.StringUtils;
import org.junit.Test;
import static org.hamcrest.Matchers.hasItemInArray;
import static org.hamcrest.Matchers.hasItems;
import static org.junit.Assert.assertThat;
/**
* Created by IntelliJ IDEA.
* User: kallestenflo
* Date: 2/2/11
* Time: 1:22 PM
*/
public class SplitPathFragmentsTest {
/*
1. "$..book[-1:].foo.bar"
2. "$.store.book[*].author"
3. "$..author"
4. "$.store.*"
5. "$.store..price"
6. "$..book[(@.length-1)]"
7. "$..book[-1:]
8. "$..book[0,1]"
9. "$..book[:2]"
10. "$..book[?(@.isbn)]"
11. "$..book[?(@.price<10)]"
12. "$..*"
*/
@Test
public void fragments_are_split_correctly() throws Exception {
assertThat(PathUtil.splitPath("$..book[-1:].foo.bar"), hasItems("$", "..", "[-1:]", "foo", "bar"));
assertThat(PathUtil.splitPath("$.store.book[*].author"), hasItems("$", "store", "book", "[*]", "author"));
assertThat(PathUtil.splitPath("$..author"), hasItems("$", "..", "author"));
assertThat(PathUtil.splitPath("$.store.*"), hasItems("$", "store", "*"));
assertThat(PathUtil.splitPath("$.store..price"), hasItems("$", "store", "..", "price"));
assertThat(PathUtil.splitPath("$..book[(@.length-1)]"), hasItems("$", "..", "book", "[(@.length-1)]"));
assertThat(PathUtil.splitPath("$..book[-1:]"), hasItems("$", "..", "book", "[-1:]"));
assertThat(PathUtil.splitPath("$..book[0,1]"), hasItems("$", "..", "book", "[0,1]"));
assertThat(PathUtil.splitPath("$..book[:2]"), hasItems("$", "..", "book", "[:2]"));
assertThat(PathUtil.splitPath("$..book[?(@.isbn)]"), hasItems("$", "..", "book", "[?(@.isbn)]"));
assertThat(PathUtil.splitPath("$..book[?(@.price<10)]"), hasItems("$", "..", "book", "[?(@.price<10)]"));
assertThat(PathUtil.splitPath("$..*"), hasItems("$", "..", "*"));
assertThat(PathUtil.splitPath("$.[0][1].author"), hasItems("$", "[0]", "[1]", "author"));
assertThat(PathUtil.splitPath("$.[0].[1].author"), hasItems("$", "[0]", "[1]", "author"));
}
}
Loading…
Cancel
Save