From f16ce7c2c65c740f59f68567527a7625f2a05600 Mon Sep 17 00:00:00 2001 From: Tamas Adam Date: Fri, 10 Apr 2015 12:03:52 +0200 Subject: [PATCH 1/7] Added renameKey feature to rename a key value found in a map path to a new key value. --- .../java/com/jayway/jsonpath/JsonPath.java | 11 ++++ .../com/jayway/jsonpath/WriteContext.java | 27 +++++++- .../jayway/jsonpath/internal/JsonReader.java | 17 +++++ .../com/jayway/jsonpath/internal/PathRef.java | 63 +++++++++++++++++-- .../java/com/jayway/jsonpath/WriteTest.java | 39 ++++++++++-- 5 files changed, 146 insertions(+), 11 deletions(-) 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 b46af94e..65b16e7a 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/JsonPath.java +++ b/json-path/src/main/java/com/jayway/jsonpath/JsonPath.java @@ -276,6 +276,17 @@ public class JsonPath { return resultByConfiguration(jsonObject, configuration, evaluationContext); } + public T renameKey(Object jsonObject, String oldKeyName, String newKeyName, Configuration configuration){ + notNull(jsonObject, "json can not be null"); + notEmpty(newKeyName, "newKeyName can not be null or empty"); + notNull(configuration, "configuration can not be null"); + EvaluationContext evaluationContext = path.evaluate(jsonObject, jsonObject, configuration, true); + for (PathRef updateOperation : evaluationContext.updateOperations()) { + updateOperation.renameKey(oldKeyName, newKeyName, configuration); + } + return resultByConfiguration(jsonObject, configuration, evaluationContext); + } + /** * Applies this JsonPath to the provided json string * diff --git a/json-path/src/main/java/com/jayway/jsonpath/WriteContext.java b/json-path/src/main/java/com/jayway/jsonpath/WriteContext.java index 0f827895..e34412c1 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/WriteContext.java +++ b/json-path/src/main/java/com/jayway/jsonpath/WriteContext.java @@ -116,6 +116,7 @@ public interface WriteContext { */ DocumentContext put(String path, String key, Object value, Predicate... filters); + /** * Add or update the key with a the given value at the given path * @@ -126,4 +127,28 @@ public interface WriteContext { */ DocumentContext put(JsonPath path, String key, Object value); -} + /** + * Renames the last key element of a given path. + * @param path The path to the old key. Should be resolved to a map + * or an array including map items. + * @param oldKeyName The old key name. + * @param newKeyName The new key name. + * @param filters filters. + * @return a document content. + */ + DocumentContext renameKey(String path, String oldKeyName, String newKeyName, Predicate... filters); + + /** + * Renames the last key element of a given path. + * @param path The path to the old key. Should be resolved to a map + * or an array including map items. + * @param oldKeyName The old key name. + * @param newKeyName The new key name. + * @return a document content. + */ + DocumentContext renameKey(JsonPath path, String oldKeyName, String newKeyName); + +// DocumentContext replace(String path, String newKey, Predicate... filters); +// DocumentContext replace(JsonPath path, String newKey); + +} \ No newline at end of file diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/JsonReader.java b/json-path/src/main/java/com/jayway/jsonpath/internal/JsonReader.java index 7f207802..b755f5fb 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/JsonReader.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/JsonReader.java @@ -231,6 +231,23 @@ public class JsonReader implements ParseContext, DocumentContext { return put(compile(path, filters), key, value); } + @Override + public DocumentContext renameKey(String path, String oldKeyName, String newKeyName, Predicate... filters) { + return renameKey(compile(path, filters), oldKeyName, newKeyName); + } + + @Override + public DocumentContext renameKey(JsonPath path, String oldKeyName, String newKeyName) { + List modified = path.renameKey(json, oldKeyName, newKeyName, configuration.addOptions(Option.AS_PATH_LIST)); + if(logger.isDebugEnabled()){ + for (String p : modified) { + logger.debug("Rename path {} new value {}", p, newKeyName); + } + } + return this; + } + + @Override public DocumentContext put(JsonPath path, String key, Object value){ List modified = path.put(json, key, value, configuration.addOptions(Option.AS_PATH_LIST)); diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/PathRef.java b/json-path/src/main/java/com/jayway/jsonpath/internal/PathRef.java index e8be7057..2c277781 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/PathRef.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/PathRef.java @@ -2,6 +2,7 @@ package com.jayway.jsonpath.internal; import com.jayway.jsonpath.Configuration; import com.jayway.jsonpath.InvalidModificationException; +import com.jayway.jsonpath.JsonPath; import com.jayway.jsonpath.spi.json.JsonProvider; import java.util.Collection; @@ -25,6 +26,10 @@ public abstract class PathRef implements Comparable { @Override public void put(String key, Object newVal, Configuration configuration) {} + + @Override + public void renameKey(String oldKeyName, String newKeyName, Configuration configuration) {} + }; protected Object parent; @@ -44,6 +49,21 @@ public abstract class PathRef implements Comparable { public abstract void put(String key, Object newVal, Configuration configuration); + public abstract void renameKey(String oldKey,String newKeyName, Configuration configuration); + + protected void renameInMap(Object targetMap, String oldKeyName, String newKeyName, Configuration configuration){ + if(configuration.jsonProvider().isMap(targetMap)){ + configuration.jsonProvider().setProperty(targetMap, newKeyName, configuration.jsonProvider().getMapValue(targetMap, oldKeyName)); + configuration.jsonProvider().removeProperty(targetMap, oldKeyName); + } else { + throw new InvalidModificationException("Can only rename properties in a map"); + } + } + + protected boolean targetInvalid(Object target){ + return target == JsonProvider.UNDEFINED || target == null; + } + @Override public int compareTo(PathRef o) { return this.getAccessor().toString().compareTo(o.getAccessor().toString()) * -1; @@ -103,6 +123,16 @@ public abstract class PathRef implements Comparable { throw new InvalidModificationException("Invalid put operation. $ is not a map"); } } + + @Override + public void renameKey(String oldKeyName, String newKeyName, Configuration configuration) { + Object target = parent; + if(targetInvalid(target)){ + return; + } + renameInMap(target, oldKeyName, newKeyName, configuration); + } + } private static class ArrayIndexPathRef extends PathRef { @@ -123,7 +153,7 @@ public abstract class PathRef implements Comparable { public void add(Object value, Configuration configuration){ Object target = configuration.jsonProvider().getArrayIndex(parent, index); - if(target == JsonProvider.UNDEFINED || target == null){ + if(targetInvalid(target)){ return; } if(configuration.jsonProvider().isArray(target)){ @@ -135,7 +165,7 @@ public abstract class PathRef implements Comparable { public void put(String key, Object value, Configuration configuration){ Object target = configuration.jsonProvider().getArrayIndex(parent, index); - if(target == JsonProvider.UNDEFINED || target == null){ + if(targetInvalid(target)){ return; } if(configuration.jsonProvider().isMap(target)){ @@ -145,6 +175,15 @@ public abstract class PathRef implements Comparable { } } + @Override + public void renameKey(String oldKeyName, String newKeyName, Configuration configuration) { + Object target = configuration.jsonProvider().getArrayIndex(parent, index); + if(targetInvalid(target)){ + return; + } + renameInMap(target, oldKeyName, newKeyName, configuration); + } + @Override public Object getAccessor() { return index; @@ -172,7 +211,7 @@ public abstract class PathRef implements Comparable { public void add(Object value, Configuration configuration){ Object target = configuration.jsonProvider().getMapValue(parent, property); - if(target == JsonProvider.UNDEFINED || target == null){ + if(targetInvalid(target)){ return; } if(configuration.jsonProvider().isArray(target)){ @@ -184,7 +223,7 @@ public abstract class PathRef implements Comparable { public void put(String key, Object value, Configuration configuration){ Object target = configuration.jsonProvider().getMapValue(parent, property); - if(target == JsonProvider.UNDEFINED || target == null){ + if(targetInvalid(target)){ return; } if(configuration.jsonProvider().isMap(target)){ @@ -194,6 +233,15 @@ public abstract class PathRef implements Comparable { } } + @Override + public void renameKey(String oldKeyName, String newKeyName, Configuration configuration) { + Object target = configuration.jsonProvider().getMapValue(parent, property); + if(targetInvalid(target)){ + return; + } + renameInMap(target, oldKeyName, newKeyName, configuration); + } + @Override public Object getAccessor() { return property; @@ -228,7 +276,12 @@ public abstract class PathRef implements Comparable { @Override public void put(String key, Object newVal, Configuration configuration) { - throw new InvalidModificationException("Add can not be performed to multiple properties"); + throw new InvalidModificationException("Put can not be performed to multiple properties"); + } + + @Override + public void renameKey(String oldKeyName, String newKeyName, Configuration configuration) { + throw new InvalidModificationException("Rename can not be performed to multiple properties"); } @Override diff --git a/json-path/src/test/java/com/jayway/jsonpath/WriteTest.java b/json-path/src/test/java/com/jayway/jsonpath/WriteTest.java index d295a8a7..e319b023 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/WriteTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/WriteTest.java @@ -2,10 +2,7 @@ package com.jayway.jsonpath; import org.junit.Test; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; +import java.util.*; import static com.jayway.jsonpath.JsonPath.parse; import static java.util.Collections.emptyMap; @@ -214,6 +211,38 @@ public class WriteTest extends BaseTest { parse(model).set("$[?(@.a == 'a-val')]", 1); } + @Test + public void a_path_can_be_renamed(){ + Object o = parse(JSON_DOCUMENT).renameKey("$.store", "book", "updated-book").json(); + List result = parse(o).read("$.store.updated-book"); + + assertThat(result).isNotEmpty(); + } + + @Test + public void keys_in_root_containing_map_can_be_renamed(){ + Object o = parse(JSON_DOCUMENT).renameKey("$", "store", "new-store").json(); + List result = parse(o).read("$.new-store[*]"); + assertThat(result).isNotEmpty(); + } + + @Test + public void map_array_items_can_be_renamed(){ + Object o = parse(JSON_DOCUMENT).renameKey("$.store.book[*]", "category", "renamed-category").json(); + List result = parse(o).read("$.store.book[*].renamed-category"); + assertThat(result).isNotEmpty(); + } + @Test(expected = InvalidModificationException.class) + public void non_map_array_items_cannot_be_renamed(){ + List model = new LinkedList(); + model.add(1); + model.add(2); + parse(model).renameKey("$[*]", "oldKey", "newKey"); + } -} + @Test(expected = InvalidModificationException.class) + public void multiple_properties_cannot_be_renamed(){ + parse(JSON_DOCUMENT).renameKey("$.store.book[*]['author', 'category']", "old-key", "new-key"); + } +} \ No newline at end of file From 4b04a1434b457761d182c49b62dfeccd359aab11 Mon Sep 17 00:00:00 2001 From: Tamas Adam Date: Mon, 13 Apr 2015 08:26:46 +0200 Subject: [PATCH 2/7] Code clenup, added mven plugin to gradle build. --- build.gradle | 1 + json-path/src/main/java/com/jayway/jsonpath/WriteContext.java | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index a4c9decb..c77aefd3 100644 --- a/build.gradle +++ b/build.gradle @@ -41,6 +41,7 @@ subprojects { apply plugin: 'java' apply plugin: 'signing' apply plugin: 'osgi' + apply plugin: 'maven' sourceCompatibility = 1.6 targetCompatibility = 1.6 diff --git a/json-path/src/main/java/com/jayway/jsonpath/WriteContext.java b/json-path/src/main/java/com/jayway/jsonpath/WriteContext.java index e34412c1..29a87bc7 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/WriteContext.java +++ b/json-path/src/main/java/com/jayway/jsonpath/WriteContext.java @@ -148,7 +148,4 @@ public interface WriteContext { */ DocumentContext renameKey(JsonPath path, String oldKeyName, String newKeyName); -// DocumentContext replace(String path, String newKey, Predicate... filters); -// DocumentContext replace(JsonPath path, String newKey); - } \ No newline at end of file From dbff1d8b48c107f57827c503b3277d03ed1563e6 Mon Sep 17 00:00:00 2001 From: Tamas Adam Date: Mon, 13 Apr 2015 11:05:56 +0200 Subject: [PATCH 3/7] Fix for rename on non-existing old key - an InvalidPathException is thrown. --- .../src/main/java/com/jayway/jsonpath/internal/PathRef.java | 4 ++++ json-path/src/test/java/com/jayway/jsonpath/WriteTest.java | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/PathRef.java b/json-path/src/main/java/com/jayway/jsonpath/internal/PathRef.java index 2c277781..c352899c 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/PathRef.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/PathRef.java @@ -2,6 +2,7 @@ package com.jayway.jsonpath.internal; import com.jayway.jsonpath.Configuration; import com.jayway.jsonpath.InvalidModificationException; +import com.jayway.jsonpath.InvalidPathException; import com.jayway.jsonpath.JsonPath; import com.jayway.jsonpath.spi.json.JsonProvider; @@ -53,6 +54,9 @@ public abstract class PathRef implements Comparable { protected void renameInMap(Object targetMap, String oldKeyName, String newKeyName, Configuration configuration){ if(configuration.jsonProvider().isMap(targetMap)){ + if(configuration.jsonProvider().getMapValue(targetMap, oldKeyName) == JsonProvider.UNDEFINED){ + throw new InvalidPathException("Key "+oldKeyName+" not found in map!"); + } configuration.jsonProvider().setProperty(targetMap, newKeyName, configuration.jsonProvider().getMapValue(targetMap, oldKeyName)); configuration.jsonProvider().removeProperty(targetMap, oldKeyName); } else { diff --git a/json-path/src/test/java/com/jayway/jsonpath/WriteTest.java b/json-path/src/test/java/com/jayway/jsonpath/WriteTest.java index e319b023..42f99354 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/WriteTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/WriteTest.java @@ -245,4 +245,9 @@ public class WriteTest extends BaseTest { public void multiple_properties_cannot_be_renamed(){ parse(JSON_DOCUMENT).renameKey("$.store.book[*]['author', 'category']", "old-key", "new-key"); } + + @Test(expected = InvalidPathException.class) + public void non_existent_key_rename_not_allowed(){ + Object o = parse(JSON_DOCUMENT).renameKey("$", "fake", "new-fake").json(); + } } \ No newline at end of file From 3cc47259fa65e6e667c7dccb9ba5ac2bae5530c3 Mon Sep 17 00:00:00 2001 From: Tamas Adam Date: Mon, 13 Apr 2015 11:14:07 +0200 Subject: [PATCH 4/7] Using the more approtiate PathNotFoundException if old key in rename map was not found. --- .../main/java/com/jayway/jsonpath/internal/PathRef.java | 7 ++----- json-path/src/test/java/com/jayway/jsonpath/WriteTest.java | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/PathRef.java b/json-path/src/main/java/com/jayway/jsonpath/internal/PathRef.java index c352899c..48b36ed9 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/PathRef.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/PathRef.java @@ -1,9 +1,6 @@ package com.jayway.jsonpath.internal; -import com.jayway.jsonpath.Configuration; -import com.jayway.jsonpath.InvalidModificationException; -import com.jayway.jsonpath.InvalidPathException; -import com.jayway.jsonpath.JsonPath; +import com.jayway.jsonpath.*; import com.jayway.jsonpath.spi.json.JsonProvider; import java.util.Collection; @@ -55,7 +52,7 @@ public abstract class PathRef implements Comparable { protected void renameInMap(Object targetMap, String oldKeyName, String newKeyName, Configuration configuration){ if(configuration.jsonProvider().isMap(targetMap)){ if(configuration.jsonProvider().getMapValue(targetMap, oldKeyName) == JsonProvider.UNDEFINED){ - throw new InvalidPathException("Key "+oldKeyName+" not found in map!"); + throw new PathNotFoundException("No results for Key "+oldKeyName+" found in map!"); } configuration.jsonProvider().setProperty(targetMap, newKeyName, configuration.jsonProvider().getMapValue(targetMap, oldKeyName)); configuration.jsonProvider().removeProperty(targetMap, oldKeyName); diff --git a/json-path/src/test/java/com/jayway/jsonpath/WriteTest.java b/json-path/src/test/java/com/jayway/jsonpath/WriteTest.java index 42f99354..6e12d71c 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/WriteTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/WriteTest.java @@ -246,7 +246,7 @@ public class WriteTest extends BaseTest { parse(JSON_DOCUMENT).renameKey("$.store.book[*]['author', 'category']", "old-key", "new-key"); } - @Test(expected = InvalidPathException.class) + @Test(expected = PathNotFoundException.class) public void non_existent_key_rename_not_allowed(){ Object o = parse(JSON_DOCUMENT).renameKey("$", "fake", "new-fake").json(); } From 48544d14ab51a57dd1c7bf34a00f4c6fe383b7a7 Mon Sep 17 00:00:00 2001 From: Tamas Adam Date: Wed, 22 Apr 2015 09:15:00 +0200 Subject: [PATCH 5/7] Fix for list element removel if multiple path elements match. --- .../main/java/com/jayway/jsonpath/internal/PathRef.java | 5 ++++- .../jayway/jsonpath/spi/json/AbstractJsonProvider.java | 8 ++++---- .../src/test/java/com/jayway/jsonpath/WriteTest.java | 9 +++++++++ 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/PathRef.java b/json-path/src/main/java/com/jayway/jsonpath/internal/PathRef.java index 48b36ed9..50c03d79 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/PathRef.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/PathRef.java @@ -4,6 +4,7 @@ import com.jayway.jsonpath.*; import com.jayway.jsonpath.spi.json.JsonProvider; import java.util.Collection; +import java.util.List; public abstract class PathRef implements Comparable { @@ -138,10 +139,12 @@ public abstract class PathRef implements Comparable { private static class ArrayIndexPathRef extends PathRef { private int index; + private Object value; private ArrayIndexPathRef(Object parent, int index) { super(parent); this.index = index; + this.value = ((List) parent).get(index); } public void set(Object newVal, Configuration configuration){ @@ -149,7 +152,7 @@ public abstract class PathRef implements Comparable { } public void delete(Configuration configuration){ - configuration.jsonProvider().removeProperty(parent, index); + configuration.jsonProvider().removeProperty(parent, value); } public void add(Object value, Configuration configuration){ diff --git a/json-path/src/main/java/com/jayway/jsonpath/spi/json/AbstractJsonProvider.java b/json-path/src/main/java/com/jayway/jsonpath/spi/json/AbstractJsonProvider.java index 0a7d7275..e8d9deaf 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/spi/json/AbstractJsonProvider.java +++ b/json-path/src/main/java/com/jayway/jsonpath/spi/json/AbstractJsonProvider.java @@ -106,16 +106,16 @@ public abstract class AbstractJsonProvider implements JsonProvider { * Removes a value in an object or array * * @param obj an array or an object - * @param key a String key or a numerical index to remove + * @param key a String key or an object in a Collection to be removed. */ @SuppressWarnings("unchecked") public void removeProperty(Object obj, Object key) { if (isMap(obj)) ((Map) obj).remove(key.toString()); else { - List list = (List) obj; - int index = key instanceof Integer ? (Integer) key : Integer.parseInt(key.toString()); - list.remove(index); + Collection collection = (Collection) obj; + //int index = key instanceof Integer ? (Integer) key : Integer.parseInt(key.toString()); + collection.remove(key); } } diff --git a/json-path/src/test/java/com/jayway/jsonpath/WriteTest.java b/json-path/src/test/java/com/jayway/jsonpath/WriteTest.java index 6e12d71c..2924d956 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/WriteTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/WriteTest.java @@ -123,6 +123,15 @@ public class WriteTest extends BaseTest { assertThat(res).containsExactly("reference"); } + @Test + public void an_array_criteria_with_multiple_results_can_be_deleted(){ + String deletePath = "$._embedded.mandates[?(@.count=~/0/)]"; + DocumentContext documentContext = JsonPath.parse(getClass().getResourceAsStream("/json_array_multiple_delete.json")); + documentContext.delete(deletePath); + List result = documentContext.read(deletePath); + assertThat(result.size()).isEqualTo(0); + } + @Test public void multi_prop_delete() { From 0042ae962489f966bc0cdc4841db6052b7376e71 Mon Sep 17 00:00:00 2001 From: Tamas Adam Date: Wed, 29 Apr 2015 09:33:19 +0200 Subject: [PATCH 6/7] Added missing test file. --- .../resources/json_array_multiple_delete.json | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 json-path/src/test/resources/json_array_multiple_delete.json diff --git a/json-path/src/test/resources/json_array_multiple_delete.json b/json-path/src/test/resources/json_array_multiple_delete.json new file mode 100644 index 00000000..a5178cb1 --- /dev/null +++ b/json-path/src/test/resources/json_array_multiple_delete.json @@ -0,0 +1,54 @@ +{ + "_embedded": { + "mandates": [ + { + "count": "0", + "difference": "+0" + }, + { + "count": "0", + "difference": "+0" + }, + { + "count": "0", + "difference": "+0" + }, + { + "count": "0", + "difference": "+0" + }, + { + "count": "0", + "difference": "+0" + }, + { + "count": "10", + "difference": "+0" + }, + { + "count": "34", + "difference": "+0" + }, + { + "count": "2", + "difference": "+0" + }, + { + "count": "4", + "difference": "+0" + }, + { + "count": "0", + "difference": "+0" + }, + { + "count": "0", + "difference": "+0" + }, + { + "count": "0", + "difference": "+0" + } + ] + } +} \ No newline at end of file From b5c175d04d78a8ba63d6bd7b7371d26d682e952b Mon Sep 17 00:00:00 2001 From: Tamas Adam Date: Wed, 29 Apr 2015 20:11:17 +0200 Subject: [PATCH 7/7] Added convert method for path value in-situ conversion. --- .../java/com/jayway/jsonpath/JsonPath.java | 18 ++++--- .../com/jayway/jsonpath/WriteContext.java | 21 ++++++++ .../jayway/jsonpath/internal/JsonReader.java | 13 +++++ .../com/jayway/jsonpath/internal/PathRef.java | 28 ++++++++++ .../jsonpath/internal/ValueConverter.java | 10 ++++ .../java/com/jayway/jsonpath/WriteTest.java | 54 +++++++++++++++++++ 6 files changed, 138 insertions(+), 6 deletions(-) create mode 100644 json-path/src/main/java/com/jayway/jsonpath/internal/ValueConverter.java 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 65b16e7a..454bb141 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/JsonPath.java +++ b/json-path/src/main/java/com/jayway/jsonpath/JsonPath.java @@ -15,12 +15,7 @@ package com.jayway.jsonpath; -import com.jayway.jsonpath.internal.EvaluationContext; -import com.jayway.jsonpath.internal.JsonReader; -import com.jayway.jsonpath.internal.Path; -import com.jayway.jsonpath.internal.PathCompiler; -import com.jayway.jsonpath.internal.PathRef; -import com.jayway.jsonpath.internal.Utils; +import com.jayway.jsonpath.internal.*; import com.jayway.jsonpath.spi.json.JsonProvider; import java.io.File; @@ -218,6 +213,17 @@ public class JsonPath { return resultByConfiguration(jsonObject, configuration, evaluationContext); } + public T convert(Object jsonObject, ValueConverter valueConverter, Configuration configuration) { + notNull(jsonObject, "json can not be null"); + notNull(configuration, "configuration can not be null"); + EvaluationContext evaluationContext = path.evaluate(jsonObject, jsonObject, configuration, true); + for (PathRef updateOperation : evaluationContext.updateOperations()) { + updateOperation.convert(valueConverter, configuration); + } + return resultByConfiguration(jsonObject, configuration, evaluationContext); + } + + /** * Deletes the object this path points to in the provided jsonObject * diff --git a/json-path/src/main/java/com/jayway/jsonpath/WriteContext.java b/json-path/src/main/java/com/jayway/jsonpath/WriteContext.java index 29a87bc7..4c617a71 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/WriteContext.java +++ b/json-path/src/main/java/com/jayway/jsonpath/WriteContext.java @@ -14,6 +14,8 @@ */ package com.jayway.jsonpath; +import com.jayway.jsonpath.internal.ValueConverter; + public interface WriteContext { /** @@ -56,6 +58,25 @@ public interface WriteContext { */ DocumentContext set(JsonPath path, Object newValue); + /** + * Converts the value on the given path. + * + * @param path path to be converted set + * @param valueConverter Converter object to be invoked (or lambda:)) + * @param filters filters + * @return a document context + */ + DocumentContext convert(String path, ValueConverter valueConverter, Predicate... filters); + + /** + * Converts the value on the given path. + * + * @param path path to be converted set + * @param valueConverter Converter object to be invoked (or lambda:)) + * @return a document context + */ + DocumentContext convert(JsonPath path, ValueConverter valueConverter); + /** * Deletes the given path * diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/JsonReader.java b/json-path/src/main/java/com/jayway/jsonpath/internal/JsonReader.java index b755f5fb..b4043b53 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/JsonReader.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/JsonReader.java @@ -33,6 +33,7 @@ import java.io.InputStream; import java.util.List; import static com.jayway.jsonpath.JsonPath.compile; +import static com.jayway.jsonpath.JsonPath.parse; import static com.jayway.jsonpath.internal.Utils.notEmpty; import static com.jayway.jsonpath.internal.Utils.notNull; @@ -194,6 +195,18 @@ public class JsonReader implements ParseContext, DocumentContext { return this; } + @Override + public DocumentContext convert(String path, ValueConverter valueConverter, Predicate... filters) { + convert(compile(path, filters), valueConverter); + return this; + } + + @Override + public DocumentContext convert(JsonPath path, ValueConverter valueConverter) { + path.convert(json, valueConverter, configuration); + return this; + } + @Override public DocumentContext delete(String path, Predicate... filters) { return delete(compile(path, filters)); diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/PathRef.java b/json-path/src/main/java/com/jayway/jsonpath/internal/PathRef.java index 50c03d79..36a61cf3 100644 --- a/json-path/src/main/java/com/jayway/jsonpath/internal/PathRef.java +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/PathRef.java @@ -17,6 +17,9 @@ public abstract class PathRef implements Comparable { @Override public void set(Object newVal, Configuration configuration) {} + @Override + public void convert(ValueConverter valueConverter, Configuration configuration) {} + @Override public void delete(Configuration configuration) {} @@ -42,6 +45,8 @@ public abstract class PathRef implements Comparable { public abstract void set(Object newVal, Configuration configuration); + public abstract void convert(ValueConverter valueConverter, Configuration configuration); + public abstract void delete(Configuration configuration); public abstract void add(Object newVal, Configuration configuration); @@ -103,6 +108,10 @@ public abstract class PathRef implements Comparable { throw new InvalidModificationException("Invalid delete operation"); } + public void convert(ValueConverter valueConverter, Configuration configuration){ + throw new InvalidModificationException("Invalid convert operation"); + } + @Override public void delete(Configuration configuration) { throw new InvalidModificationException("Invalid delete operation"); @@ -136,6 +145,7 @@ public abstract class PathRef implements Comparable { } } + private static class ArrayIndexPathRef extends PathRef { private int index; @@ -151,6 +161,11 @@ public abstract class PathRef implements Comparable { configuration.jsonProvider().setArrayIndex(parent, index, newVal); } + public void convert(ValueConverter valueConverter, Configuration configuration){ + Object currentValue = configuration.jsonProvider().getArrayIndex(parent, index); + configuration.jsonProvider().setArrayIndex(parent, index, valueConverter.convert(currentValue)); + } + public void delete(Configuration configuration){ configuration.jsonProvider().removeProperty(parent, value); } @@ -209,6 +224,13 @@ public abstract class PathRef implements Comparable { configuration.jsonProvider().setProperty(parent, property, newVal); } + @Override + public void convert(ValueConverter valueConverter, Configuration configuration) { + Object currentValue = configuration.jsonProvider().getMapValue(parent, property); + configuration.jsonProvider().setProperty(parent, property, valueConverter.convert(currentValue)); + } + + public void delete(Configuration configuration){ configuration.jsonProvider().removeProperty(parent, property); } @@ -266,6 +288,12 @@ public abstract class PathRef implements Comparable { configuration.jsonProvider().setProperty(parent, property, newVal); } } + public void convert(ValueConverter valueConverter, Configuration configuration) { + for (String property : properties) { + Object currentValue = configuration.jsonProvider().getMapValue(parent, property); + configuration.jsonProvider().setProperty(parent, property, valueConverter.convert(currentValue)); + } + } public void delete(Configuration configuration){ for (String property : properties) { diff --git a/json-path/src/main/java/com/jayway/jsonpath/internal/ValueConverter.java b/json-path/src/main/java/com/jayway/jsonpath/internal/ValueConverter.java new file mode 100644 index 00000000..ca2b31b3 --- /dev/null +++ b/json-path/src/main/java/com/jayway/jsonpath/internal/ValueConverter.java @@ -0,0 +1,10 @@ +package com.jayway.jsonpath.internal; + +/** + * Created by tom on 29/04/15. + */ +public interface ValueConverter { + + public Object convert(Object currentValue); + +} diff --git a/json-path/src/test/java/com/jayway/jsonpath/WriteTest.java b/json-path/src/test/java/com/jayway/jsonpath/WriteTest.java index 2924d956..aa4e2446 100644 --- a/json-path/src/test/java/com/jayway/jsonpath/WriteTest.java +++ b/json-path/src/test/java/com/jayway/jsonpath/WriteTest.java @@ -1,5 +1,6 @@ package com.jayway.jsonpath; +import com.jayway.jsonpath.internal.ValueConverter; import org.junit.Test; import java.util.*; @@ -259,4 +260,57 @@ public class WriteTest extends BaseTest { public void non_existent_key_rename_not_allowed(){ Object o = parse(JSON_DOCUMENT).renameKey("$", "fake", "new-fake").json(); } + + @Test(expected = InvalidModificationException.class) + public void rootCannotBeConverted(){ + ValueConverter valueConverter = new ValueConverter() { + @Override + public Object convert(Object currentValue) { + return currentValue.toString()+"converted"; + } + }; + Object o = parse(JSON_DOCUMENT).convert("$", valueConverter).json(); + } + + @Test + public void single_match_value_can_be_converted(){ + ValueConverter valueConverter = new ToStringValueConverterImpl(); + String stringResult = parse(JSON_DOCUMENT).convert("$.string-property", valueConverter).read("$.string-property"); + assertThat(stringResult.endsWith("converted")).isTrue(); + } + + @Test + public void object_can_be_converted(){ + ValueConverter valueConverter = new ToStringValueConverterImpl(); + DocumentContext documentContext = parse(JSON_DOCUMENT); + Object list = documentContext.read("$..book"); + assertThat(list).isInstanceOf(List.class); + String result = ((List)documentContext.convert("$..book", valueConverter).read("$..book")).get(0); + assertThat(result).isInstanceOf(String.class); + assertThat(((String)result).endsWith("converted")).isTrue(); + } + + @Test + public void multi_match_path_can_be_converted(){ + ValueConverter valueConverter = new ToStringValueConverterImpl(); + List doubleResult = parse(JSON_DOCUMENT).read("$..display-price"); + for(Double dRes : doubleResult){ + assertThat(dRes).isInstanceOf(Double.class); + } + List stringResult = parse(JSON_DOCUMENT).convert("$..display-price", valueConverter).read("$..display-price"); + for(String sRes : stringResult){ + assertThat(sRes).isInstanceOf(String.class); + assertThat(sRes.endsWith("converted")).isTrue(); + } + } + + // Helper converter implementation for test cases. + private class ToStringValueConverterImpl implements ValueConverter{ + + @Override + public Object convert(Object currentValue) { + return currentValue.toString()+"converted"; + } + } + } \ No newline at end of file