From 24e8311c9cef8f8a9e154397e5a670e96bb21f22 Mon Sep 17 00:00:00 2001 From: Kalle Stenflo Date: Mon, 12 Mar 2012 09:27:49 +0100 Subject: [PATCH] Docs and refactorings. --- .../java/com/jayway/jsonpath/Criteria.java | 12 +- .../jsonpath/InvalidConversionException.java | 14 + .../java/com/jayway/jsonpath/JsonModel.java | 627 +++++++++++++++--- .../java/com/jayway/jsonpath/JsonPath.java | 18 +- .../java/com/jayway/jsonpath/Transformer.java | 30 +- .../jsonpath/internal/ConvertUtils.java | 78 +++ .../com/jayway/jsonpath/internal/IOUtils.java | 32 + .../jayway/jsonpath/internal/PathToken.java | 21 + .../jsonpath/internal/PathTokenizer.java | 12 +- .../com/jayway/jsonpath/internal/Util.java | 10 - .../internal/filter/ArrayIndexFilter.java | 2 +- .../com/jayway/jsonpath/spi/HttpProvider.java | 7 + .../jsonpath/spi/HttpProviderFactory.java | 28 +- .../com/jayway/jsonpath/spi/JsonProvider.java | 2 + .../jsonpath/spi/JsonProviderFactory.java | 1 + .../spi/impl/AbstractJsonProvider.java | 7 + .../jsonpath/spi/impl/JacksonProvider.java | 12 +- .../com/jayway/jsonpath/ComplianceTest.java | 30 +- .../com/jayway/jsonpath/HttpProviderTest.java | 38 ++ .../com/jayway/jsonpath/JsonModelOpsTest.java | 145 +++- .../JsonModelSubModelDetachedTest.java | 65 ++ .../jsonpath/JsonModelSubModelTest.java | 116 ++++ .../com/jayway/jsonpath/JsonModelTest.java | 21 +- .../com/jayway/jsonpath/JsonProviderTest.java | 20 +- .../java/com/jayway/jsonpath/PathTest.java | 13 +- 25 files changed, 1207 insertions(+), 154 deletions(-) create mode 100644 json-path/src/main/java/com/jayway/jsonpath/InvalidConversionException.java create mode 100644 json-path/src/main/java/com/jayway/jsonpath/internal/ConvertUtils.java create mode 100644 json-path/src/main/java/com/jayway/jsonpath/internal/IOUtils.java delete mode 100644 json-path/src/main/java/com/jayway/jsonpath/internal/Util.java create mode 100644 json-path/src/test/java/com/jayway/jsonpath/JsonModelSubModelDetachedTest.java create mode 100644 json-path/src/test/java/com/jayway/jsonpath/JsonModelSubModelTest.java diff --git a/json-path/src/main/java/com/jayway/jsonpath/Criteria.java b/json-path/src/main/java/com/jayway/jsonpath/Criteria.java index 81d4fc18..cfb57c7b 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/Criteria.java +++ b/json-path/src/main/java/com/jayway/jsonpath/Criteria.java @@ -27,11 +27,6 @@ import static org.apache.commons.lang.Validate.notNull; public class Criteria { - /** - * Custom "not-null" object as we have to be able to work with {@literal null} values as well. - */ - private static final Object NOT_SET = new Object(); - private enum CriteriaType { GT, GTE, @@ -48,6 +43,11 @@ public class Criteria { OR } + /** + * Custom "not-null" object as we have to be able to work with {@literal null} values as well. + */ + private static final Object NOT_SET = new Object(); + private final String key; private final List criteriaChain; @@ -234,8 +234,6 @@ public class Criteria { } return true; } else { - - if (isValue == null) { return (map.get(key) == null); } else { diff --git a/json-path/src/main/java/com/jayway/jsonpath/InvalidConversionException.java b/json-path/src/main/java/com/jayway/jsonpath/InvalidConversionException.java new file mode 100644 index 00000000..aa7d1ca6 --- /dev/null +++ b/json-path/src/main/java/com/jayway/jsonpath/InvalidConversionException.java @@ -0,0 +1,14 @@ +package com.jayway.jsonpath; + +/** + * Created by IntelliJ IDEA. + * User: kallestenflo + * Date: 3/12/12 + * Time: 7:49 AM + */ +public class InvalidConversionException extends RuntimeException { + + public InvalidConversionException(String message) { + super(message); + } +} 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 f8891c73..4dc12167 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/JsonModel.java +++ b/json-path/src/main/java/com/jayway/jsonpath/JsonModel.java @@ -14,8 +14,9 @@ */ package com.jayway.jsonpath; +import com.jayway.jsonpath.internal.ConvertUtils; import com.jayway.jsonpath.internal.PathToken; -import com.jayway.jsonpath.internal.Util; +import com.jayway.jsonpath.internal.IOUtils; import com.jayway.jsonpath.spi.JsonProvider; import com.jayway.jsonpath.spi.JsonProviderFactory; import com.jayway.jsonpath.spi.MappingProviderFactory; @@ -26,17 +27,18 @@ import java.net.URL; import java.util.*; import static java.util.Arrays.asList; -import static org.apache.commons.lang.Validate.notEmpty; -import static org.apache.commons.lang.Validate.notNull; +import static org.apache.commons.lang.Validate.*; /** - * A JsonModel represents a parsed JSON document that provides easy and efficient read operations. In contrast to the + * A JsonModel holds a parsed JSON document and provides easy read and write operations. In contrast to the * static read operations provided by {@link JsonPath} a JsonModel will only parse the document once. * * @author Kalle Stenflo */ public class JsonModel { + + private static final JsonPath JSON_PATH_ROOT = JsonPath.compile("$"); private Object jsonObject; private JsonProvider jsonProvider; @@ -98,23 +100,71 @@ public class JsonModel { this.jsonObject = jsonProvider.parse(jsonInputStream); this.jsonProvider = jsonProvider; } finally { - Util.closeQuietly(jsonInputStream); + IOUtils.closeQuietly(jsonInputStream); } } - public boolean isList(){ + /** + * Check if this JsonModel is holding a JSON array as to object + * + * @return true if root is an array + */ + public boolean isList() { return jsonProvider.isList(jsonObject); } - public boolean isMap(){ + /** + * Check if this JsonModel is holding a JSON object as to object + * + * @return true if root is an object + */ + public boolean isMap() { return jsonProvider.isMap(jsonObject); } + /** + * Check if this JsonModel has the given definite path + * + * @see com.jayway.jsonpath.JsonPath#isPathDefinite() + * + * @param jsonPath path to check + * @return true if model contains path + */ + public boolean hasPath(String jsonPath){ + return hasPath(JsonPath.compile(jsonPath)); + } + + /** + * Check if this JsonModel has the given definite path + * + * @see com.jayway.jsonpath.JsonPath#isPathDefinite() + * + * @param jsonPath path to check + * @return true if model contains path + */ + public boolean hasPath(JsonPath jsonPath){ + + isTrue(jsonPath.isPathDefinite(), "hasPath can only be used for definite paths"); + + try { + get(jsonPath); + } catch(InvalidPathException e){ + return false; + } + return true; + } + // -------------------------------------------------------- // // Getters // // -------------------------------------------------------- + + /** + * Returns the root object of this JsonModel + * + * @return returns the root object + */ public Object getJsonObject() { return this.jsonObject; } @@ -125,11 +175,51 @@ public class JsonModel { // // -------------------------------------------------------- + /** + * Reads the given path from this JsonModel. Filters is a way to problematically filter the contents of a list. + * Instead of writing the filter criteria directly inside the JsonPath expression the filter is indicated and + * provided as an argument. + *

+ * All three statements below are equivalent + *

+ * + * JsonModel model = JsonModel.model(myJson); + *

+ * //A + * List books = model.read("$store.book[?(@author == 'Nigel Rees')]"); + *

+ * //B + * List books = model.read("$store.book[?]", filter(where("author").is("Nigel Rees")); + *

+ * //C + * JsonPath path = JsonPath.compile("$store.book[?]", filter(where("author").is("Nigel Rees")); + *

+ * List books = model.read(path); + *

+ * + *

+ * The filters are applied in the order they are provided. If a path contains multiple [?] filter markers + * the filters must be passed in the correct order. + * + * @param jsonPath the path to read + * @param filters filters to use in the path + * @param expected return type + * @return the json path result + * @see Filter + * @see Criteria + */ @SuppressWarnings({"unchecked"}) public T get(String jsonPath, Filter... filters) { return (T) get(JsonPath.compile(jsonPath, filters)); } + /** + * Reads the given path from this JsonModel. + * + * @param jsonPath the path to read + * @param expected return type + * @return the json path result + */ @SuppressWarnings({"unchecked"}) public T get(JsonPath jsonPath) { notNull(jsonPath, "jsonPath can not be null"); @@ -142,36 +232,80 @@ public class JsonModel { // Model writers // // -------------------------------------------------------- - public ArrayOps opsForArray(String jsonPath) { - return opsForArray(JsonPath.compile(jsonPath)); - } + /** + * Gets an {@link ArrayOps} for this JsonModel. Note that the root element of this model + * must be a json array. + * + * @return array operations for this JsonModel + */ public ArrayOps opsForArray() { - return new DefaultArrayOps(this.jsonObject); + isTrue(jsonProvider.isList(jsonObject), "This JsonModel is not a JSON array"); + return opsForArray(JSON_PATH_ROOT); } + /** + * Gets an {@link ArrayOps} for the array inside this JsonModel identified by the given JsonPath. The path must + * be definite ({@link com.jayway.jsonpath.JsonPath#isPathDefinite()}). + *

+ * Note that the element returned by the given path must be a json array. + * + * @param jsonPath definite path to array to perform operations on + * @return array operations for the targeted array + */ + public ArrayOps opsForArray(String jsonPath) { + return opsForArray(JsonPath.compile(jsonPath)); + } + + /** + * Gets an {@link ArrayOps} for the array inside this JsonModel identified by the given JsonPath. The path must + * be definite ({@link com.jayway.jsonpath.JsonPath#isPathDefinite()}). + *

+ * Note that the element returned by the given path must be a json array. + * + * @param jsonPath definite path to array to perform operations on + * @return array operations for the targeted array + */ public ArrayOps opsForArray(JsonPath jsonPath) { notNull(jsonPath, "jsonPath can not be null"); - - Object opsTarget = getTargetObject(jsonPath, List.class); - - return new DefaultArrayOps(opsTarget); + return new DefaultArrayOps(jsonPath); } + /** + * Gets an {@link ObjectOps} for this JsonModel. Note that the root element of this model + * must be a json object. + * + * @return object operations for this JsonModel + */ public ObjectOps opsForObject() { - return new DefaultObjectOps(this.jsonObject); + return opsForObject(JSON_PATH_ROOT); } + /** + * Gets an {@link ObjectOps} for the object inside this JsonModel identified by the given JsonPath. The path must + * be definite ({@link com.jayway.jsonpath.JsonPath#isPathDefinite()}). + *

+ * Note that the element returned by the given path must be a json object. + * + * @param jsonPath definite path to object to perform operations on + * @return object operations for the targeted object + */ public ObjectOps opsForObject(String jsonPath) { return opsForObject(JsonPath.compile(jsonPath)); } + /** + * Gets an {@link ObjectOps} for the object inside this JsonModel identified by the given JsonPath. The path must + * be definite ({@link com.jayway.jsonpath.JsonPath#isPathDefinite()}). + *

+ * Note that the element returned by the given path must be a json object. + * + * @param jsonPath definite path to object to perform operations on + * @return object operations for the targeted object + */ public ObjectOps opsForObject(JsonPath jsonPath) { notNull(jsonPath, "jsonPath can not be null"); - - Object opsTarget = getTargetObject(jsonPath, Map.class); - - return new DefaultObjectOps(opsTarget); + return new DefaultObjectOps(jsonPath); } // -------------------------------------------------------- @@ -179,15 +313,31 @@ public class JsonModel { // JSON extractors // // -------------------------------------------------------- - public String getJson() { + + /** + * Creates a JSON representation of this JsonModel + * + * @return model as Json + */ + public String toJson() { return jsonProvider.toJson(jsonObject); } - public String getJson(String jsonPath, Filter... filters) { - return getJson(JsonPath.compile(jsonPath, filters)); + /** + * Creates a JSON representation of the result of the provided JsonPath + * + * @return path result as Json + */ + public String toJson(String jsonPath, Filter... filters) { + return toJson(JsonPath.compile(jsonPath, filters)); } - public String getJson(JsonPath jsonPath) { + /** + * Creates a JSON representation of the result of the provided JsonPath + * + * @return path result as Json + */ + public String toJson(JsonPath jsonPath) { notNull(jsonPath, "jsonPath can not be null"); return jsonProvider.toJson(get(jsonPath)); @@ -199,19 +349,83 @@ public class JsonModel { // // -------------------------------------------------------- - public JsonModel getModel(String jsonPath, Filter... filters) { - return getModel(JsonPath.compile(jsonPath, filters)); + /** + * Returns a sub model from this JsonModel. A sub model can be any JSON object or JSON array + * addressed by a definite path. In contrast to a detached model changes on the sub model + * will be applied on the source model (the JsonModel from which the sub model was created) + * + * + * @param jsonPath the absolute path to extract a JsonModel for + * @return the new JsonModel + * + * @see com.jayway.jsonpath.JsonPath#isPathDefinite() + */ + public JsonModel getSubModel(String jsonPath) { + return getSubModel(JsonPath.compile(jsonPath)); } - public JsonModel getModel(JsonPath jsonPath) { + /** + * Returns a sub model from this JsonModel. A sub model can be any JSON object or JSON array + * addressed by a definite path. In contrast to a detached model changes on the sub model + * will be applied on the source model (the JsonModel from which the sub model was created) + * + * + * @param jsonPath the absolute path to extract a JsonModel for + * @return the new JsonModel + * + * @see com.jayway.jsonpath.JsonPath#isPathDefinite() + */ + public JsonModel getSubModel(JsonPath jsonPath) { notNull(jsonPath, "jsonPath can not be null"); + isTrue(jsonPath.isPathDefinite(), "You can only get subModels with a definite path. Use getDetachedModel if path is not definite."); + Object subModel = jsonPath.read(jsonObject); - if(!jsonProvider.isContainer(subModel)){ + if (!jsonProvider.isContainer(subModel)) { throw new InvalidModelException("The path " + jsonPath.getPath() + " returned an invalid model " + (subModel != null ? subModel.getClass() : "null")); } + return new JsonSubModel(subModel, this.jsonProvider, this, jsonPath); + } + // -------------------------------------------------------- + // + // Detached sub model readers + // + // -------------------------------------------------------- + + /** + * Creates a detached sub model from this JsonModel. A detached sub model does not have + * to be created using a definite path. Changes on a detached sub model will not be reflected on the + * source model (the JsonModel from which the sub model was created). + * + * @param jsonPath the absolute path to extract a JsonModel for + * @param filters filters to expand the path + * @return a detached JsonModel + */ + public JsonModel getSubModelDetached(String jsonPath, Filter... filters) { + return getSubModelDetached(JsonPath.compile(jsonPath, filters)); + } + + /** + * Creates a detached sub model from this JsonModel. A detached sub model does not have + * to be created using a definite path. Changes on a detached sub model will not be reflected on the + * source model (the JsonModel from which the sub model was created). + * + * @param jsonPath the absolute path to extract a JsonModel for + * @return a detached JsonModel + */ + public JsonModel getSubModelDetached(JsonPath jsonPath) { + notNull(jsonPath, "jsonPath can not be null"); + + Object subModel = jsonPath.read(jsonObject); + + if (!jsonProvider.isContainer(subModel)) { + throw new InvalidModelException("The path " + jsonPath.getPath() + " returned an invalid model " + (subModel != null ? subModel.getClass() : "null")); + } + + subModel = jsonProvider.clone(subModel); + return new JsonModel(subModel, this.jsonProvider); } @@ -220,14 +434,33 @@ public class JsonModel { // Mapping model readers // // -------------------------------------------------------- + + /** + * Returns a {@link MappingModelReader} for this JsonModel. Note that to use this functionality you need + * an optional dependencies on your classpath (jackson-mapper-asl ver >= 1.9.5) + * + * @return a object mapper + */ public MappingModelReader map() { return new DefaultMappingModelReader(this.jsonObject); } + /** + * Returns a {@link MappingModelReader} for the JsonModel targeted by the provided {@link JsonPath}. Note that to use this functionality you need + * an optional dependencies on your classpath (jackson-mapper-asl ver >= 1.9.5) + * + * @return a object mapper + */ public MappingModelReader map(String jsonPath, Filter... filters) { return map(JsonPath.compile(jsonPath, filters)); } + /** + * Returns a {@link MappingModelReader} for the JsonModel targeted by the provided {@link JsonPath}. Note that to use this functionality you need + * an optional dependencies on your classpath (jackson-mapper-asl ver >= 1.9.5) + * + * @return a object mapper + */ public MappingModelReader map(JsonPath jsonPath) { notNull(jsonPath, "jsonPath can not be null"); @@ -239,24 +472,49 @@ public class JsonModel { // Static factory methods // // -------------------------------------------------------- + + /** + * Creates a JsonModel + * + * @param json json string + * @return a new JsonModel + */ public static JsonModel model(String json) { notEmpty(json, "json can not be null or empty"); return new JsonModel(json, JsonProviderFactory.createProvider()); } + /** + * Creates a JsonModel + * + * @param jsonObject a json container (a {@link Map} or a {@link List}) + * @return a new JsonModel + */ public static JsonModel model(Object jsonObject) { notNull(jsonObject, "jsonObject can not be null"); return new JsonModel(jsonObject, JsonProviderFactory.createProvider()); } + /** + * Creates a JsonModel + * + * @param url pointing to a Json document + * @return a new JsonModel + */ public static JsonModel model(URL url) throws IOException { notNull(url, "url can not be null"); return new JsonModel(url, JsonProviderFactory.createProvider()); } - + + /** + * Creates a JsonModel + * + * @param jsonInputStream json document stream + * @return a new JsonModel + */ public static JsonModel model(InputStream jsonInputStream) throws IOException { notNull(jsonInputStream, "jsonInputStream can not be null"); @@ -280,18 +538,50 @@ public class JsonModel { Object modelRef = jsonObject; - LinkedList tokens = jsonPath.getTokenizer().getPathTokens(); + if (jsonPath.getTokenizer().size() == 1) { + PathToken onlyToken = jsonPath.getTokenizer().iterator().next(); + if ("$".equals(onlyToken.getFragment())) { + return clazz.cast(modelRef); + } + } else { + + LinkedList tokens = jsonPath.getTokenizer().getPathTokens(); + + PathToken currentToken; + do { + currentToken = tokens.poll(); + modelRef = currentToken.apply(modelRef, jsonProvider); + } while (!tokens.isEmpty()); + + if (modelRef.getClass().isAssignableFrom(clazz)) { + throw new InvalidModelException(jsonPath + " does nor refer to a Map but " + currentToken.getClass().getName()); + } + return clazz.cast(modelRef); + } + throw new InvalidModelException(); + } + + private void setTargetObject(JsonPath jsonPath, Object newValue) { + JsonPath setterPath = jsonPath.copy(); + PathToken pathToken = setterPath.getTokenizer().removeLastPathToken(); - PathToken currentToken; - do { - currentToken = tokens.poll(); - modelRef = currentToken.apply(modelRef, jsonProvider); - } while (!tokens.isEmpty()); - if (modelRef.getClass().isAssignableFrom(clazz)) { - throw new InvalidModelException(jsonPath + " does nor refer to a Map but " + currentToken.getClass().getName()); + if (pathToken.isRootToken()) { + if (this instanceof JsonSubModel) { + JsonSubModel thisModel = (JsonSubModel) this; + + thisModel.parent.setTargetObject(thisModel.subModelPath, newValue); + } else { + this.jsonObject = newValue; + } + } else { + if (pathToken.isArrayIndexToken()) { + int arrayIndex = pathToken.getArrayIndex(); + opsForArray(setterPath).set(arrayIndex, newValue); + } else { + opsForObject(setterPath).put(pathToken.getFragment(), newValue); + } } - return clazz.cast(modelRef); } // -------------------------------------------------------- @@ -300,178 +590,353 @@ public class JsonModel { // // -------------------------------------------------------- - + /** + * Converts a {@link JsonModel} to an Object + */ public interface ObjectMappingModelReader { + /** + * Converts this JsonModel to the specified class using the configured {@link com.jayway.jsonpath.spi.MappingProvider} + * + * @see MappingProviderFactory + * + * @param targetClass class to convert the {@link JsonModel} to + * @param template class + * @return the mapped model + */ T to(Class targetClass); } + /** + * Converts a {@link JsonModel} to an {@link Collection} of Objects + */ public interface ListMappingModelReader { + /** + * Converts this JsonModel to the a list of objects with the provided class using the configured {@link com.jayway.jsonpath.spi.MappingProvider} + * + * @param targetClass class to convert the {@link JsonModel} array items to + * @param template class + * @return the mapped mode + */ List of(Class targetClass); + /** + * Syntactic sugar function to use with {@link ListMappingModelReader#of} + */ ListMappingModelReader toList(); + /** + * Converts this JsonModel to the a {@link List} of objects with the provided class using the configured {@link com.jayway.jsonpath.spi.MappingProvider} + * + * @param targetClass class to convert the {@link JsonModel} array items to + * @param template class + * @return the mapped mode + */ List toListOf(Class targetClass); + /** + * Converts this JsonModel to the a {@link Set} of objects with the provided class using the configured {@link com.jayway.jsonpath.spi.MappingProvider} + * + * @param targetClass class to convert the {@link JsonModel} array items to + * @param template class + * @return the mapped model + */ Set toSetOf(Class targetClass); } + /** + * Object mapping interface used when for root object that can be either a {@link List} or a {@link Map}. + * It's up to the invoker to know what the conversion target can be mapped to. + */ public interface MappingModelReader extends ListMappingModelReader, ObjectMappingModelReader { - } + /** + * Operations that can be performed on Json objects ({@link Map}s) + */ public interface ObjectOps { + /** + * Returns the operation target + * @return the operation target + */ Map getTarget(); + /** + * @see Map#containsKey(Object) + */ boolean containsKey(String key); + /** + * @see Map#put(Object, Object) + */ ObjectOps put(String key, Object value); + /** + * Adds the value to the target map if it is not already present + * @param key the key + * @param value the value + * @return this {@link ObjectOps} + */ ObjectOps putIfAbsent(String key, Object value); + /** + * @see Map#get(Object) + */ Object get(String key); + /** + * Tries to convert the value associated with the key to an {@link Integer} + * @param key the key + * @return converted value + */ + Integer getInteger(String key); + + /** + * Tries to convert the value associated with the key to an {@link Long} + * @param key the key + * @return converted value + */ + Long getLong(String key); + + /** + * Tries to convert the value associated with the key to an {@link Double} + * @param key the key + * @return converted value + */ + Double getDouble(String key); + + /** + * @see Map#putAll(java.util.Map) + */ ObjectOps putAll(Map map); + /** + * @see Map#remove(Object) + */ ObjectOps remove(String key); - ObjectOps transform(Transformer transformer); - + /** + * Allows transformations of the target object. the target for this {@link ObjectOps} will be be replaced + * with the {@link Object} returned by the {@link Transformer#transform(Object)} + * + * @param transformer the transformer to use + * @return this {@link ObjectOps} + */ + ObjectOps transform(Transformer> transformer); + + /** + * Map the target of this {@link ObjectOps} to the provided class + * @param targetClass class to convert the target object to + * @param template class + * @return the mapped model + */ T to(Class targetClass); } + /** + * Operations that can be performed on Json arrays ({@link List}s) + */ public interface ArrayOps { + + /** + * Returns the operation target + * @return the operation target + */ List getTarget(); + /** + * @see List#add(Object) + */ ArrayOps add(Object o); + /** + * @see List#addAll(java.util.Collection) + */ ArrayOps addAll(Collection collection); + /** + * @see List#remove(int) + */ ArrayOps remove(Object o); + /** + * @see java.util.List#size() + */ + int size(); + + /** + * @see List#set(int, Object) + */ + ArrayOps set(int index, Object value); + + /** + * Allows transformations of the target list. The target for this {@link ArrayOps} will be be replaced + * with the {@link Object} returned by the {@link Transformer#transform(Object)} + * + * @param transformer the transformer to use + * @return this {@link ArrayOps} + */ + ArrayOps transform(Transformer> transformer); + + /** + * @see ListMappingModelReader + */ ListMappingModelReader toList(); - ArrayOps transform(Transformer transformer); - + /** + * @see ListMappingModelReader + */ List toListOf(Class targetClass); + /** + * @see ListMappingModelReader + */ Set toSetOf(Class targetClass); } - private static class DefaultObjectOps implements ObjectOps { + private class DefaultObjectOps implements ObjectOps { - private Map opsTarget; + private JsonPath jsonPath; - private DefaultObjectOps(Object opsTarget) { - this.opsTarget = (Map) opsTarget; + private DefaultObjectOps(JsonPath jsonPath) { + this.jsonPath = jsonPath; } @Override public Map getTarget() { - return opsTarget; + return getTargetObject(jsonPath, Map.class); } @Override public boolean containsKey(String key) { - return opsTarget.containsKey(key); + return getTargetObject(jsonPath, Map.class).containsKey(key); } @Override public ObjectOps put(String key, Object value) { - opsTarget.put(key, value); + getTargetObject(jsonPath, Map.class).put(key, value); return this; } @Override public ObjectOps putIfAbsent(String key, Object value) { - if (!opsTarget.containsKey(key)) { - opsTarget.put(key, value); + Map targetObject = getTargetObject(jsonPath, Map.class); + if (!targetObject.containsKey(key)) { + targetObject.put(key, value); } return this; } @Override public Object get(String key) { - return opsTarget.get(key); + return getTargetObject(jsonPath, Map.class).get(key); + } + + @Override + public Integer getInteger(String key) { + return ConvertUtils.toInt(get(key)); + } + + @Override + public Long getLong(String key) { + return ConvertUtils.toLong(get(key)); + } + + @Override + public Double getDouble(String key) { + return ConvertUtils.toDouble(get(key)); } @Override public ObjectOps putAll(Map map) { - opsTarget.putAll(map); + getTargetObject(jsonPath, Map.class).putAll(map); return this; } @Override public ObjectOps remove(String key) { - opsTarget.remove(key); + getTargetObject(jsonPath, Map.class).remove(key); return this; } @Override - public ObjectOps transform(Transformer transformer) { - transformer.transform(-1, JsonModel.model(opsTarget)); + public ObjectOps transform(Transformer> transformer) { + Map targetObject = getTargetObject(jsonPath, Map.class); + Object transformed = transformer.transform(targetObject); + setTargetObject(jsonPath, transformed); return this; } @Override public T to(Class targetClass) { - return new DefaultMappingModelReader(opsTarget).to(targetClass); + Map targetObject = getTargetObject(jsonPath, Map.class); + return new DefaultMappingModelReader(targetObject).to(targetClass); } } - private static class DefaultArrayOps implements ArrayOps { - private List opsTarget; + private class DefaultArrayOps implements ArrayOps { + + private JsonPath jsonPath; - private DefaultArrayOps(Object opsTarget) { - this.opsTarget = (List) opsTarget; + private DefaultArrayOps(JsonPath jsonPath) { + this.jsonPath = jsonPath; } @Override public List getTarget() { - return opsTarget; + return getTargetObject(jsonPath, List.class); } @Override public ArrayOps add(Object o) { - opsTarget.add(o); + getTargetObject(jsonPath, List.class).add(o); return this; } @Override public ArrayOps addAll(Collection collection) { - opsTarget.addAll(collection); + getTargetObject(jsonPath, List.class).addAll(collection); return this; } @Override public ArrayOps remove(Object o) { - opsTarget.remove(o); + getTargetObject(jsonPath, List.class).remove(o); + return this; + } + + @Override + public int size() { + return getTargetObject(jsonPath, List.class).size(); + } + + @Override + public ArrayOps set(int index, Object value) { + getTargetObject(jsonPath, List.class).set(index, value); return this; } @Override public ListMappingModelReader toList() { - return new DefaultMappingModelReader(opsTarget); + return new DefaultMappingModelReader(getTargetObject(jsonPath, List.class)); } @Override - public ArrayOps transform(Transformer transformer) { - for (int i = 0; i < opsTarget.size(); i++) { - Object current = opsTarget.get(i); - opsTarget.set(i, transformer.transform(i, current)); - } + public ArrayOps transform(Transformer> transformer) { + Object transformed = transformer.transform(getTargetObject(jsonPath, List.class)); + setTargetObject(jsonPath, transformed); return this; } @Override public List toListOf(Class targetClass) { - return new DefaultMappingModelReader(opsTarget).toListOf(targetClass); + return new DefaultMappingModelReader(getTargetObject(jsonPath, List.class)).toListOf(targetClass); } @Override public Set toSetOf(Class targetClass) { - return new DefaultMappingModelReader(opsTarget).toSetOf(targetClass); + return new DefaultMappingModelReader(getTargetObject(jsonPath, List.class)).toSetOf(targetClass); } } @@ -517,4 +982,16 @@ public class JsonModel { return MappingProviderFactory.createProvider().convertValue(model, targetClass); } } + + private static class JsonSubModel extends JsonModel { + + private final JsonModel parent; + private final JsonPath subModelPath; + + private JsonSubModel(Object jsonObject, JsonProvider jsonProvider, JsonModel parent, JsonPath subModelPath) { + super(jsonObject, jsonProvider); + this.parent = parent; + this.subModelPath = subModelPath; + } + } } 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 bb0dcd9a..099f25a7 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/JsonPath.java +++ b/json-path/src/main/java/com/jayway/jsonpath/JsonPath.java @@ -17,11 +17,12 @@ package com.jayway.jsonpath; import com.jayway.jsonpath.internal.PathToken; import com.jayway.jsonpath.internal.PathTokenizer; -import com.jayway.jsonpath.internal.Util; +import com.jayway.jsonpath.internal.IOUtils; import com.jayway.jsonpath.internal.filter.PathTokenFilter; import com.jayway.jsonpath.spi.HttpProviderFactory; import com.jayway.jsonpath.spi.JsonProvider; import com.jayway.jsonpath.spi.JsonProviderFactory; +import org.apache.commons.lang.StringUtils; import java.io.*; import java.net.URL; @@ -108,8 +109,11 @@ public class JsonPath { throw new InvalidPathException("Invalid path"); } + + int filterCountInPath = StringUtils.countMatches(jsonPath, "[?]"); + isTrue(filterCountInPath == filters.length, "Filters in path ([?]) does not match provided filters."); + this.tokenizer = new PathTokenizer(jsonPath); - this.filters = new LinkedList(); this.filters.addAll(asList(filters)); @@ -120,6 +124,10 @@ public class JsonPath { } + public JsonPath copy(){ + return new JsonPath(tokenizer.getPath(), filters.toArray(new Filter[0])); + } + /** * Returns the string representation of this JsonPath @@ -220,7 +228,7 @@ public class JsonPath { in = HttpProviderFactory.getProvider().get(jsonURL); return (T) read(JsonProviderFactory.createProvider().parse(in)); } finally { - Util.closeQuietly(in); + IOUtils.closeQuietly(in); } } @@ -242,7 +250,7 @@ public class JsonPath { fis = new FileInputStream(jsonFile); return (T) read(JsonProviderFactory.createProvider().parse(fis)); } finally { - Util.closeQuietly(fis); + IOUtils.closeQuietly(fis); } } @@ -261,7 +269,7 @@ public class JsonPath { try { return (T) read(JsonProviderFactory.createProvider().parse(jsonInputStream)); } finally { - Util.closeQuietly(jsonInputStream); + IOUtils.closeQuietly(jsonInputStream); } } diff --git a/json-path/src/main/java/com/jayway/jsonpath/Transformer.java b/json-path/src/main/java/com/jayway/jsonpath/Transformer.java index 05598531..2f2a7457 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/Transformer.java +++ b/json-path/src/main/java/com/jayway/jsonpath/Transformer.java @@ -1,13 +1,31 @@ +/* + * Copyright 2011 the original author or authors. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.jayway.jsonpath; /** - * Created by IntelliJ IDEA. - * User: kallestenflo - * Date: 3/9/12 - * Time: 3:20 PM + * @author Kalle Stenflo */ -public interface Transformer { +public interface Transformer { + + /** + * + * @param obj object to transform + * @return the transformed object + */ + public Object transform(T obj); + - public JsonModel transform(JsonModel model); } diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/ConvertUtils.java b/json-path/src/main/java/com/jayway/jsonpath/internal/ConvertUtils.java new file mode 100644 index 00000000..eb5c4582 --- /dev/null +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/ConvertUtils.java @@ -0,0 +1,78 @@ +/* + * Copyright 2011 the original author or authors. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jayway.jsonpath.internal; + +import com.jayway.jsonpath.InvalidConversionException; + +/** + * @author Kalle Stenflo + */ +public class ConvertUtils { + + /** + * converts to Integer with radix 10 + * + * @param o object to convert + * @return converted value + */ + public static Integer toInt(Object o) { + if (null == o) + return null; + if (o instanceof Number) + return ((Number) o).intValue(); + try { + return Integer.valueOf(o.toString().trim(), 10); + } catch (Exception e) { + throw new InvalidConversionException("Could not convert " + o.toString() + " to Integer"); + } + } + + + /** + * converts to Long with radix 10 + * + * @param o object to convert + * @return converted value + */ + public static Long toLong(Object o) { + if (null == o) + return null; + if (o instanceof Number) + return ((Number) o).longValue(); + try { + return Long.valueOf(o.toString().trim(), 10); + } catch (Exception e) { + throw new InvalidConversionException("Could not convert " + o.toString() + " to Long"); + } + } + + /** + * converts to Double with radix 10 + * + * @param o object to convert + * @return converted value + */ + public static Double toDouble(Object o) { + if (null == o) + return null; + if (o instanceof Number) + return ((Number) o).doubleValue(); + try { + return Double.valueOf(o.toString().trim()); + } catch (Exception e) { + throw new InvalidConversionException("Could not convert " + o.toString() + " to Double"); + } + } +} diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/IOUtils.java b/json-path/src/main/java/com/jayway/jsonpath/internal/IOUtils.java new file mode 100644 index 00000000..3a0dc4fe --- /dev/null +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/IOUtils.java @@ -0,0 +1,32 @@ +/* + * Copyright 2011 the original author or authors. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jayway.jsonpath.internal; + +import java.io.Closeable; +import java.io.IOException; + +/** + * @author Kalle Stenflo + */ +public abstract class IOUtils { + + public static void closeQuietly(Closeable closeable) { + try { + if (closeable != null) { + closeable.close(); + } + } catch (IOException ignore) {} + } +} 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 f337b5d7..5c49ff40 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 @@ -14,14 +14,20 @@ */ package com.jayway.jsonpath.internal; +import com.jayway.jsonpath.InvalidModelException; import com.jayway.jsonpath.internal.filter.FilterFactory; import com.jayway.jsonpath.internal.filter.PathTokenFilter; import com.jayway.jsonpath.spi.JsonProvider; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + /** * @author Kalle Stenflo */ public class PathToken { + + private static final Pattern ARRAY_INDEX_PATTERN = Pattern.compile("\\[(\\d+)\\]"); private String fragment; @@ -44,4 +50,19 @@ public class PathToken { public String getFragment() { return fragment; } + + public boolean isRootToken(){ + return "$".equals(fragment); + } + public boolean isArrayIndexToken(){ + return ARRAY_INDEX_PATTERN.matcher(fragment).matches(); + } + + public int getArrayIndex(){ + Matcher matcher = ARRAY_INDEX_PATTERN.matcher(fragment); + if(matcher.find()){ + return Integer.parseInt(matcher.group(1)); + } + else throw new InvalidModelException("Could not get array index from fragment " + fragment); + } } 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 d08cfbff..2e4980aa 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 @@ -50,6 +50,10 @@ public class PathTokenizer implements Iterable { return fragments; } + public int size(){ + return pathTokens.size(); + } + public String getPath() { return new String(pathChars); } @@ -62,6 +66,13 @@ public class PathTokenizer implements Iterable { return pathTokens.iterator(); } + public PathToken removeLastPathToken(){ + PathToken lastPathToken = pathTokens.get(pathTokens.size() - 1); + + //TODO: this should also trim the pathChars + pathTokens.remove(pathTokens.size() - 1); + return lastPathToken; + } //-------------------------------------------- // @@ -111,7 +122,6 @@ public class PathTokenizer implements Iterable { default: fragments.add(extract(false, '[', '.')); - } } return fragments; diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/Util.java b/json-path/src/main/java/com/jayway/jsonpath/internal/Util.java deleted file mode 100644 index 15fe2039..00000000 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/Util.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.jayway.jsonpath.internal; - -/** - * Created by IntelliJ IDEA. - * User: kallestenflo - * Date: 3/10/12 - * Time: 7:25 AM - */ -public class Util { -} 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 45266f4b..211431c1 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 @@ -54,7 +54,7 @@ public class ArrayIndexFilter extends PathTokenFilter { return result; } else if (trimmedCondition.endsWith(":")) { - trimmedCondition = trim(trimmedCondition, 1, 1); + trimmedCondition = trim(trimmedCondition.replace(" ", ""), 1, 1); int get = Integer.parseInt(trimmedCondition); return src.get(src.size() - get); diff --git a/json-path/src/main/java/com/jayway/jsonpath/spi/HttpProvider.java b/json-path/src/main/java/com/jayway/jsonpath/spi/HttpProvider.java index 9fe02f3a..aa1bee71 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/spi/HttpProvider.java +++ b/json-path/src/main/java/com/jayway/jsonpath/spi/HttpProvider.java @@ -1,5 +1,9 @@ package com.jayway.jsonpath.spi; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + /** * Created by IntelliJ IDEA. * User: kallestenflo @@ -7,4 +11,7 @@ package com.jayway.jsonpath.spi; * Time: 7:38 AM */ public interface HttpProvider { + + InputStream get(URL url) throws IOException; + } diff --git a/json-path/src/main/java/com/jayway/jsonpath/spi/HttpProviderFactory.java b/json-path/src/main/java/com/jayway/jsonpath/spi/HttpProviderFactory.java index f9587e99..7c5af9f2 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/spi/HttpProviderFactory.java +++ b/json-path/src/main/java/com/jayway/jsonpath/spi/HttpProviderFactory.java @@ -1,10 +1,36 @@ package com.jayway.jsonpath.spi; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; + /** * Created by IntelliJ IDEA. * User: kallestenflo * Date: 3/10/12 * Time: 7:39 AM */ -public class HttpProviderFactory { +public abstract class HttpProviderFactory { + + public static HttpProviderFactory factory = new HttpProviderFactory() { + @Override + protected HttpProvider create() { + return new HttpProvider() { + @Override + public InputStream get(URL url) throws IOException { + URLConnection connection = url.openConnection(); + connection.setRequestProperty("Accept", "application/json"); + return connection.getInputStream(); + } + }; + } + }; + + public static HttpProvider getProvider(){ + return factory.create(); + } + + + protected abstract HttpProvider create(); } diff --git a/json-path/src/main/java/com/jayway/jsonpath/spi/JsonProvider.java b/json-path/src/main/java/com/jayway/jsonpath/spi/JsonProvider.java index 2c933dfc..67d008b4 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/spi/JsonProvider.java +++ b/json-path/src/main/java/com/jayway/jsonpath/spi/JsonProvider.java @@ -41,6 +41,8 @@ public interface JsonProvider { List createList(); + + Object clone(Object model); /** * checks if object is instanceof java.util.List or java.util.Map diff --git a/json-path/src/main/java/com/jayway/jsonpath/spi/JsonProviderFactory.java b/json-path/src/main/java/com/jayway/jsonpath/spi/JsonProviderFactory.java index 9ad37d29..8f813181 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/spi/JsonProviderFactory.java +++ b/json-path/src/main/java/com/jayway/jsonpath/spi/JsonProviderFactory.java @@ -25,6 +25,7 @@ public abstract class JsonProviderFactory { @Override protected JsonProvider create() { return new JsonSmartJsonProvider(); + //return new JacksonProvider(); } }; diff --git a/json-path/src/main/java/com/jayway/jsonpath/spi/impl/AbstractJsonProvider.java b/json-path/src/main/java/com/jayway/jsonpath/spi/impl/AbstractJsonProvider.java index 6d8bb622..10951b84 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/spi/impl/AbstractJsonProvider.java +++ b/json-path/src/main/java/com/jayway/jsonpath/spi/impl/AbstractJsonProvider.java @@ -15,7 +15,9 @@ package com.jayway.jsonpath.spi.impl; import com.jayway.jsonpath.spi.JsonProvider; +import org.apache.commons.lang.SerializationUtils; +import java.io.Serializable; import java.util.List; import java.util.Map; @@ -24,6 +26,11 @@ import java.util.Map; */ public abstract class AbstractJsonProvider implements JsonProvider { + @Override + public Object clone(Object obj){ + return SerializationUtils.clone((Serializable)obj); + } + /** * checks if object is instanceof java.util.List or java.util.Map * diff --git a/json-path/src/main/java/com/jayway/jsonpath/spi/impl/JacksonProvider.java b/json-path/src/main/java/com/jayway/jsonpath/spi/impl/JacksonProvider.java index 7ddc41f6..f70aa747 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/spi/impl/JacksonProvider.java +++ b/json-path/src/main/java/com/jayway/jsonpath/spi/impl/JacksonProvider.java @@ -17,12 +17,14 @@ package com.jayway.jsonpath.spi.impl; import com.jayway.jsonpath.InvalidJsonException; import com.jayway.jsonpath.spi.MappingProvider; import com.jayway.jsonpath.spi.Mode; +import org.codehaus.jackson.JsonGenerator; import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.map.type.CollectionType; import java.io.IOException; import java.io.InputStream; import java.io.Reader; +import java.io.StringWriter; import java.util.*; /** @@ -66,7 +68,15 @@ public class JacksonProvider extends AbstractJsonProvider implements MappingProv @Override public String toJson(Object obj) { - throw new UnsupportedOperationException(); + StringWriter writer = new StringWriter(); + try { + JsonGenerator jsonGenerator = objectMapper.getJsonFactory().createJsonGenerator(writer); + objectMapper.writeValue(jsonGenerator, obj); + writer.close(); + return writer.getBuffer().toString(); + } catch (IOException e) { + throw new InvalidJsonException(); + } } @Override diff --git a/json-path/src/test/java/com/jayway/jsonpath/ComplianceTest.java b/json-path/src/test/java/com/jayway/jsonpath/ComplianceTest.java index ddea1dbe..96ab8378 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/ComplianceTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/ComplianceTest.java @@ -17,8 +17,8 @@ public class ComplianceTest { @Test public void test_one() throws Exception { - String json = "{ a: \"a\",\n" + - " b: \"b\",\n" + + String json = "{ \"a\": \"a\",\n" + + " \"b\": \"b\",\n" + " \"c d\": \"e\" \n" + " }"; @@ -39,23 +39,23 @@ public class ComplianceTest { assertThat(JsonPath.read(json, "$[0]"), is(equalTo(1))); assertThat(JsonPath.read(json, "$[4]"), is(equalTo(null))); assertThat(JsonPath.>read(json, "$[*]"), hasItems( - (Comparable)new Integer(1), - (Comparable)new String("2"), - (Comparable)new Double(3.14), - (Comparable)new Boolean(true), - null)); + new Integer(1), + new String("2"), + new Double(3.14), + new Boolean(true), + (Comparable)null)); assertThat(JsonPath.read(json, "$[-1:]"), is(equalTo(null))); } @Test public void test_three() throws Exception { - String json = "{ points: [\n" + - " { id: \"i1\", x: 4, y: -5 },\n" + - " { id: \"i2\", x: -2, y: 2, z: 1 },\n" + - " { id: \"i3\", x: 8, y: 3 },\n" + - " { id: \"i4\", x: -6, y: -1 },\n" + - " { id: \"i5\", x: 0, y: 2, z: 1 },\n" + - " { id: \"i6\", x: 1, y: 4 }\n" + + String json = "{ \"points\": [\n" + + " { \"id\": \"i1\", \"x\": 4, \"y\": -5 },\n" + + " { \"id\": \"i2\", \"x\": -2, \"y\": 2, \"z\": 1 },\n" + + " { \"id\": \"i3\", \"x\": 8, \"y\": 3 },\n" + + " { \"id\": \"i4\", \"x\": -6, \"y\": -1 },\n" + + " { \"id\": \"i5\", \"x\": 0, \"y\": 2, \"z\": 1 },\n" + + " { \"id\": \"i6\", \"x\": 1, \"y\": 4 }\n" + " ]\n" + " }"; @@ -74,7 +74,7 @@ public class ComplianceTest { assertThat(JsonPath.>read(json, "$.points[?(@.z)].id"), hasItems("i2", "i5")); - assertThat(JsonPath.read(json, "$.points[(@.length-1)].id"), equalTo("i6")); + assertThat(JsonPath.read(json, "$.points[(@.length - 1)].id"), equalTo("i6")); //assertThat(JsonPath.>read(json, "$['points'][?(@.x * @.x + @.y * @.y > 50)].id"), hasItems(?)); //low } diff --git a/json-path/src/test/java/com/jayway/jsonpath/HttpProviderTest.java b/json-path/src/test/java/com/jayway/jsonpath/HttpProviderTest.java index 9f7e85b8..1b732ef6 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/HttpProviderTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/HttpProviderTest.java @@ -1,5 +1,15 @@ package com.jayway.jsonpath; +import com.jayway.jsonpath.internal.IOUtils; +import com.jayway.jsonpath.spi.HttpProviderFactory; +import org.junit.Test; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +import static junit.framework.Assert.assertEquals; + /** * Created by IntelliJ IDEA. * User: kallestenflo @@ -7,4 +17,32 @@ package com.jayway.jsonpath; * Time: 8:12 AM */ public class HttpProviderTest { + + + private static final String EXPECTED = "{\n" + + " \"results\" : [],\n" + + " \"status\" : \"REQUEST_DENIED\"\n" + + "}"; + + @Test + public void http_get() throws Exception { + + URL url = new URL("http://maps.googleapis.com/maps/api/geocode/json"); + + InputStream inputStream = null; + try { + inputStream = HttpProviderFactory.getProvider().get(url); + + byte[] bytes = sun.misc.IOUtils.readFully(inputStream, -1, true); + + String json = new String(bytes).trim(); + + assertEquals(EXPECTED, json); + + } catch (IOException e) { + IOUtils.closeQuietly(inputStream); + } + + } + } diff --git a/json-path/src/test/java/com/jayway/jsonpath/JsonModelOpsTest.java b/json-path/src/test/java/com/jayway/jsonpath/JsonModelOpsTest.java index 2a939c66..1c12c165 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/JsonModelOpsTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/JsonModelOpsTest.java @@ -2,11 +2,9 @@ package com.jayway.jsonpath; import org.junit.Test; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; +import static com.jayway.jsonpath.JsonModel.model; import static junit.framework.Assert.assertEquals; /** @@ -47,11 +45,28 @@ public class JsonModelOpsTest { " \"color\": \"red\",\n" + " \"price\": 19.95,\n" + " \"foo:bar\": \"fooBar\",\n" + + " \"number\": 12,\n" + " \"dot.notation\": \"new\"\n" + " }\n" + " }\n" + "}"; + @Test + public void convert_values() throws Exception { + + JsonModel model = model(DOCUMENT).getSubModel("store.bicycle"); + + JsonModel.ObjectOps ops = model.opsForObject(); + + assertEquals(19.95D, ops.getDouble("price")); + assertEquals(new Long(12), ops.getLong("number")); + assertEquals(new Integer(12), ops.getInteger("number")); + + int i = ops.getInteger("number"); + long l = ops.getLong("number"); + double d = ops.getDouble("price"); + } + @Test public void object_ops_can_update() throws Exception { @@ -64,12 +79,12 @@ public class JsonModelOpsTest { assertEquals("Kalle", model.get("store.book[0].author")); assertEquals(12.30D, model.get("store.book[0].price")); } - - + + @Test public void array_ops_can_add_element() throws Exception { JsonModel model = JsonModel.model(DOCUMENT); - + Map newBook = new HashMap(); newBook.put("category", "reference"); newBook.put("author", "Kalle"); @@ -79,7 +94,7 @@ public class JsonModelOpsTest { model.opsForArray("store.book").add(newBook); - JsonModel subModel = model.getModel("store.book[4]"); + JsonModel subModel = model.getSubModel("store.book[4]"); assertEquals("reference", subModel.get("category")); assertEquals("Kalle", subModel.get("author")); @@ -88,6 +103,75 @@ public class JsonModelOpsTest { assertEquals(12.10D, subModel.get("price")); } + @Test + public void ops_can_transform_object_root() throws Exception { + + Map rootDocument = new HashMap(); + rootDocument.put("category", "reference"); + rootDocument.put("author", "Kalle"); + rootDocument.put("title", "JSONPath book"); + rootDocument.put("isbn", "0-553-21311-34"); + rootDocument.put("price", 12.10D); + + JsonModel model = JsonModel.model(rootDocument); + + model.opsForObject().transform(new Transformer>() { + @Override + public Object transform(Map obj) { + obj.put("name", "kalle"); + return obj; + } + }); + assertEquals("kalle", model.get("name")); + } + + + @Test + public void ops_can_transform_array_root() throws Exception { + + List rootDocument = new ArrayList(); + rootDocument.add(Collections.singletonMap("name", "kalle")); + rootDocument.add(Collections.singletonMap("name", "bob")); + rootDocument.add(Collections.singletonMap("name", "zak")); + + JsonModel model = JsonModel.model(rootDocument); + + model.opsForArray().transform(new Transformer>() { + @Override + public Object transform(List obj) { + return Collections.singletonMap("root", "new"); + } + }); + assertEquals("new", model.get("root")); + } + + @Test + public void ops_can_transform_nested_document() throws Exception { + + Map childDocument = new HashMap(); + childDocument.put("level", 1); + + Map rootDocument = new HashMap(); + rootDocument.put("category", "reference"); + rootDocument.put("author", "Kalle"); + rootDocument.put("title", "JSONPath book"); + rootDocument.put("isbn", "0-553-21311-34"); + rootDocument.put("price", 12.10D); + rootDocument.put("child", childDocument); + + JsonModel model = JsonModel.model(rootDocument); + + model.opsForObject("child").transform(new Transformer>() { + @Override + public Object transform(Map obj) { + obj.put("name", "kalle"); + return obj; + } + }); + + assertEquals("kalle", model.get("child.name")); + } + @Test public void arrays_can_be_mapped() throws Exception { JsonModel model = JsonModel.model(DOCUMENT); @@ -118,10 +202,10 @@ public class JsonModelOpsTest { @Test public void object_can_be_transformed() throws Exception { - Transformer transformer = new Transformer() { + Transformer transformer = new Transformer>() { @Override - public JsonModel transform(int index, JsonModel model) { - model.opsForObject().put("newProp", "newProp"); + public Map transform(Map model) { + model.put("newProp", "newProp"); return model; } }; @@ -135,11 +219,15 @@ public class JsonModelOpsTest { @Test public void arrays_can_be_transformed() throws Exception { - Transformer transformer = new Transformer() { + Transformer transformer = new Transformer>() { @Override - public Object transform(int index, Object model) { - Map map = (Map) model; - map.put("newProp", "newProp"); + public Object transform(List model) { + + for (Object o : model) { + Map map = (Map) o; + map.put("newProp", "newProp"); + } + return model; } }; @@ -150,26 +238,37 @@ public class JsonModelOpsTest { assertEquals("newProp", model.get("store.book[1].newProp")); } - + @Test public void array_can_be_transformed_to_primitives() throws Exception { - Transformer positionTransformer = new Transformer() { + Transformer positionTransformer = new Transformer>() { private int i = 0; + @Override - public Object transform(int index, Object model) { - return i++; + public Object transform(List model) { + List newList = new ArrayList(); + + for (Object o : model) { + newList.add(new Integer(i++)); + } + + return newList; } }; - Transformer multiplyingTransformer = new Transformer() { + Transformer multiplyingTransformer = new Transformer>() { @Override - public Object transform(int index, Object model) { - int in = (Integer)model; + public Object transform(List model) { - return in * 2; + for (int i = 0; i < model.size(); i++) { + int curr = (Integer) model.get(i); + model.set(i, curr * 2); + } + return model; } }; + JsonModel model = JsonModel.model(DOCUMENT); model.opsForArray("store.book").transform(positionTransformer).transform(multiplyingTransformer); diff --git a/json-path/src/test/java/com/jayway/jsonpath/JsonModelSubModelDetachedTest.java b/json-path/src/test/java/com/jayway/jsonpath/JsonModelSubModelDetachedTest.java new file mode 100644 index 00000000..4834b483 --- /dev/null +++ b/json-path/src/test/java/com/jayway/jsonpath/JsonModelSubModelDetachedTest.java @@ -0,0 +1,65 @@ +package com.jayway.jsonpath; + +import org.junit.Test; +import static com.jayway.jsonpath.JsonModel.model; +import static junit.framework.Assert.assertEquals; + +/** + * Created by IntelliJ IDEA. + * User: kallestenflo + * Date: 3/11/12 + * Time: 4:44 PM + */ +public class JsonModelSubModelDetachedTest { + + 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" + + " \"foo:bar\": \"fooBar\",\n" + + " \"dot.notation\": \"new\"\n" + + " }\n" + + " }\n" + + "}"; + + @Test + public void a_sub_model_can_be_detached() throws Exception { + + JsonModel model = model(DOCUMENT); + + JsonModel detachedModel = model.getSubModelDetached("$.store.book[*]"); + + detachedModel.opsForArray().add(1); + + assertEquals(4, model.opsForArray("$.store.book").size()); + assertEquals(5, detachedModel.opsForArray("$.store.book").size()); + + } + + +} diff --git a/json-path/src/test/java/com/jayway/jsonpath/JsonModelSubModelTest.java b/json-path/src/test/java/com/jayway/jsonpath/JsonModelSubModelTest.java new file mode 100644 index 00000000..5c075852 --- /dev/null +++ b/json-path/src/test/java/com/jayway/jsonpath/JsonModelSubModelTest.java @@ -0,0 +1,116 @@ +package com.jayway.jsonpath; + +import org.junit.Test; + +import java.util.Collections; +import java.util.Map; + +import static junit.framework.Assert.assertEquals; + +/** + * Created by IntelliJ IDEA. + * User: kallestenflo + * Date: 3/11/12 + * Time: 5:07 PM + */ +public class JsonModelSubModelTest { + + + 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" + + " \"foo:bar\": \"fooBar\",\n" + + " \"dot.notation\": \"new\",\n" + + " \"book\": { \"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" + + " }\n" + + " }\n" + + "}"; + + + @Test(expected = IllegalArgumentException.class) + public void a_sub_model_path_must_be_definite() throws Exception { + JsonModel.model(DOCUMENT).getSubModel("$store.book[*]"); + } + + @Test + public void test_a_sub_model_can_be_fetched_and_read() throws Exception { + JsonModel model = JsonModel.model(DOCUMENT); + assertEquals("Nigel Rees", model.getSubModel("$store.book[0]").get("author")); + assertEquals("Nigel Rees", model.getSubModel(JsonPath.compile("$store.book[0]")).get("author")); + } + + + @Test + public void when_a_sub_model_is_updated_the_master_model_is_updated() throws Exception { + + JsonModel model = JsonModel.model(DOCUMENT); + + JsonModel subModel = model.getSubModel("store.book[0]"); + subModel.opsForObject().put("author", "kalle"); + + assertEquals("kalle", model.get("store.book[0].author")); + } + + @Test + public void when_a_sub_model_root_is_transformed_the_master_model_is_updated() throws Exception { + + JsonModel model = JsonModel.model(DOCUMENT); + + JsonModel subModel = model.getSubModel("store.book[0]"); + subModel.opsForObject().transform(new Transformer>() { + @Override + public Object transform(Map obj) { + return Collections.singletonMap("prop", "new"); + } + }); + assertEquals("new", model.get("store.book[0].prop")); + } + + @Test + public void when_a_sub_model_child_is_transformed_the_master_model_is_updated() throws Exception { + + JsonModel model = JsonModel.model(DOCUMENT); + + JsonModel subModel = model.getSubModel("store.bicycle.book"); + subModel.opsForObject().transform(new Transformer>() { + @Override + public Object transform(Map obj) { + return Collections.singletonMap("prop", "new"); + } + }); + assertEquals("new", model.get("store.bicycle.book.prop")); + } + +} diff --git a/json-path/src/test/java/com/jayway/jsonpath/JsonModelTest.java b/json-path/src/test/java/com/jayway/jsonpath/JsonModelTest.java index c46e1613..9a12d1e3 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/JsonModelTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/JsonModelTest.java @@ -10,6 +10,8 @@ import java.util.Map; import static java.util.Arrays.asList; import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; /** * Created by IntelliJ IDEA. @@ -54,6 +56,16 @@ public class JsonModelTest { " }\n" + "}"; + + @Test + public void has_path_validates() throws Exception { + assertFalse(JsonModel.model(DOCUMENT).hasPath("store.invalid")); + assertFalse( JsonModel.model(DOCUMENT).hasPath("store.book[0].foo")); + + assertTrue( JsonModel.model(DOCUMENT).hasPath("store.book")); + assertTrue( JsonModel.model(DOCUMENT).hasPath("store.book[0].title")); + } + @Test public void a_json_document_can_be_fetched_with_a_URL() throws Exception { URL url = new URL("http://maps.googleapis.com/maps/api/geocode/json"); @@ -66,12 +78,7 @@ public class JsonModelTest { assertEquals("Nigel Rees", JsonModel.model(bis).get("store.book[0].author")); } - @Test - public void test_a_sub_model_can_be_fetched_and_read() throws Exception { - JsonModel model = JsonModel.model(DOCUMENT); - assertEquals("Nigel Rees", model.getModel("$store.book[0]").get("author")); - assertEquals("Nigel Rees", model.getModel(JsonPath.compile("$store.book[0]")).get("author")); - } + @Test public void maps_and_list_can_queried() throws Exception { @@ -83,7 +90,7 @@ public class JsonModelTest { assertEquals("value", model.get("$child.key")); assertEquals(1, model.get("$items[1]")); - assertEquals("{\"child\":{\"key\":\"value\"},\"items\":[0,1,2]}", model.getJson()); + assertEquals("{\"child\":{\"key\":\"value\"},\"items\":[0,1,2]}", model.toJson()); } diff --git a/json-path/src/test/java/com/jayway/jsonpath/JsonProviderTest.java b/json-path/src/test/java/com/jayway/jsonpath/JsonProviderTest.java index 08d9e612..c87d56b0 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/JsonProviderTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/JsonProviderTest.java @@ -1,8 +1,13 @@ package com.jayway.jsonpath; import com.jayway.jsonpath.spi.impl.JacksonProvider; +import org.apache.commons.lang.SerializationUtils; import org.junit.Test; +import java.io.Serializable; + +import static com.jayway.jsonpath.JsonModel.model; + /** * Created by IntelliJ IDEA. * User: kallestenflo @@ -49,6 +54,19 @@ public class JsonProviderTest { "}"; + + @Test + public void clone_test() throws Exception { + + Serializable jsonObject = (Serializable) model(DOCUMENT).getJsonObject(); + + Object clone = SerializationUtils.clone(jsonObject); + + System.out.println(model(clone).toJson()); + + } + + @Test public void parse_document() throws Exception { @@ -62,7 +80,7 @@ public class JsonProviderTest { @Test public void parse_array() throws Exception { - JacksonProvider provider = new JacksonProvider(); + JacksonProvider provider = new JacksonProvider(); Object o = provider.parse(ARRAY); diff --git a/json-path/src/test/java/com/jayway/jsonpath/PathTest.java b/json-path/src/test/java/com/jayway/jsonpath/PathTest.java index 9c653fb3..23b4cf64 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/PathTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/PathTest.java @@ -15,11 +15,22 @@ import static org.junit.Assert.*; */ public class PathTest { + Filter filter = new Filter(){ + @Override + public boolean accept(Object obj) { + return true; + } + @Override + public Filter addCriteria(Criteria criteria) { + return this; + } + }; + @Test public void path_is_not_definite() throws Exception { assertFalse(JsonPath.compile("$..book[0]").isPathDefinite()); - assertFalse(JsonPath.compile("$book[?]").isPathDefinite()); + assertFalse(JsonPath.compile("$book[?]", filter).isPathDefinite()); assertFalse(JsonPath.compile("$.books[*]").isPathDefinite()); }