Browse Source

Merge branch 'master' of git://github.com/atomcat1978/JsonPath into atomcat1978-master

pull/183/merge
Kalle Stenflo 9 years ago
parent
commit
95e4940935
  1. 1
      build.gradle
  2. 29
      json-path/src/main/java/com/jayway/jsonpath/JsonPath.java
  3. 43
      json-path/src/main/java/com/jayway/jsonpath/WriteContext.java
  4. 30
      json-path/src/main/java/com/jayway/jsonpath/internal/JsonReader.java
  5. 101
      json-path/src/main/java/com/jayway/jsonpath/internal/PathRef.java
  6. 10
      json-path/src/main/java/com/jayway/jsonpath/internal/ValueConverter.java
  7. 8
      json-path/src/main/java/com/jayway/jsonpath/spi/json/AbstractJsonProvider.java
  8. 105
      json-path/src/test/java/com/jayway/jsonpath/WriteTest.java
  9. 54
      json-path/src/test/resources/json_array_multiple_delete.json

1
build.gradle

@ -41,6 +41,7 @@ subprojects {
apply plugin: 'java' apply plugin: 'java'
apply plugin: 'signing' apply plugin: 'signing'
apply plugin: 'osgi' apply plugin: 'osgi'
apply plugin: 'maven'
sourceCompatibility = 1.6 sourceCompatibility = 1.6
targetCompatibility = 1.6 targetCompatibility = 1.6

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

@ -15,12 +15,7 @@
package com.jayway.jsonpath; package com.jayway.jsonpath;
import com.jayway.jsonpath.internal.EvaluationContext; import com.jayway.jsonpath.internal.*;
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.spi.json.JsonProvider; import com.jayway.jsonpath.spi.json.JsonProvider;
import java.io.File; import java.io.File;
@ -226,6 +221,17 @@ public class JsonPath {
return resultByConfiguration(jsonObject, configuration, evaluationContext); return resultByConfiguration(jsonObject, configuration, evaluationContext);
} }
public <T> 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 * Deletes the object this path points to in the provided jsonObject
* *
@ -284,6 +290,17 @@ public class JsonPath {
return resultByConfiguration(jsonObject, configuration, evaluationContext); return resultByConfiguration(jsonObject, configuration, evaluationContext);
} }
public <T> 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 * Applies this JsonPath to the provided json string
* *

43
json-path/src/main/java/com/jayway/jsonpath/WriteContext.java

@ -14,6 +14,8 @@
*/ */
package com.jayway.jsonpath; package com.jayway.jsonpath;
import com.jayway.jsonpath.internal.ValueConverter;
public interface WriteContext { public interface WriteContext {
/** /**
@ -56,6 +58,25 @@ public interface WriteContext {
*/ */
DocumentContext set(JsonPath path, Object newValue); 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 * Deletes the given path
* *
@ -116,6 +137,7 @@ public interface WriteContext {
*/ */
DocumentContext put(String path, String key, Object value, Predicate... filters); DocumentContext put(String path, String key, Object value, Predicate... filters);
/** /**
* Add or update the key with a the given value at the given path * Add or update the key with a the given value at the given path
* *
@ -126,4 +148,25 @@ public interface WriteContext {
*/ */
DocumentContext put(JsonPath path, String key, Object value); 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);
} }

30
json-path/src/main/java/com/jayway/jsonpath/internal/JsonReader.java

@ -36,6 +36,7 @@ import java.util.LinkedList;
import java.util.List; import java.util.List;
import static com.jayway.jsonpath.JsonPath.compile; 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.notEmpty;
import static com.jayway.jsonpath.internal.Utils.notNull; import static com.jayway.jsonpath.internal.Utils.notNull;
import static java.util.Arrays.asList; import static java.util.Arrays.asList;
@ -212,6 +213,18 @@ public class JsonReader implements ParseContext, DocumentContext {
return this; 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 @Override
public DocumentContext delete(String path, Predicate... filters) { public DocumentContext delete(String path, Predicate... filters) {
return delete(compile(path, filters)); return delete(compile(path, filters));
@ -249,6 +262,23 @@ public class JsonReader implements ParseContext, DocumentContext {
return put(compile(path, filters), key, value); 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<String> 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 @Override
public DocumentContext put(JsonPath path, String key, Object value){ public DocumentContext put(JsonPath path, String key, Object value){
List<String> modified = path.put(json, key, value, configuration.addOptions(Option.AS_PATH_LIST)); List<String> modified = path.put(json, key, value, configuration.addOptions(Option.AS_PATH_LIST));

101
json-path/src/main/java/com/jayway/jsonpath/internal/PathRef.java

@ -1,10 +1,10 @@
package com.jayway.jsonpath.internal; package com.jayway.jsonpath.internal;
import com.jayway.jsonpath.Configuration; import com.jayway.jsonpath.*;
import com.jayway.jsonpath.InvalidModificationException;
import com.jayway.jsonpath.spi.json.JsonProvider; import com.jayway.jsonpath.spi.json.JsonProvider;
import java.util.Collection; import java.util.Collection;
import java.util.List;
public abstract class PathRef implements Comparable<PathRef> { public abstract class PathRef implements Comparable<PathRef> {
@ -17,6 +17,9 @@ public abstract class PathRef implements Comparable<PathRef> {
@Override @Override
public void set(Object newVal, Configuration configuration) {} public void set(Object newVal, Configuration configuration) {}
@Override
public void convert(ValueConverter valueConverter, Configuration configuration) {}
@Override @Override
public void delete(Configuration configuration) {} public void delete(Configuration configuration) {}
@ -25,6 +28,10 @@ public abstract class PathRef implements Comparable<PathRef> {
@Override @Override
public void put(String key, Object newVal, Configuration configuration) {} public void put(String key, Object newVal, Configuration configuration) {}
@Override
public void renameKey(String oldKeyName, String newKeyName, Configuration configuration) {}
}; };
protected Object parent; protected Object parent;
@ -38,12 +45,32 @@ public abstract class PathRef implements Comparable<PathRef> {
public abstract void set(Object newVal, Configuration configuration); 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 delete(Configuration configuration);
public abstract void add(Object newVal, Configuration configuration); public abstract void add(Object newVal, Configuration configuration);
public abstract void put(String key, Object newVal, Configuration configuration); 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)){
if(configuration.jsonProvider().getMapValue(targetMap, oldKeyName) == JsonProvider.UNDEFINED){
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);
} else {
throw new InvalidModificationException("Can only rename properties in a map");
}
}
protected boolean targetInvalid(Object target){
return target == JsonProvider.UNDEFINED || target == null;
}
@Override @Override
public int compareTo(PathRef o) { public int compareTo(PathRef o) {
return this.getAccessor().toString().compareTo(o.getAccessor().toString()) * -1; return this.getAccessor().toString().compareTo(o.getAccessor().toString()) * -1;
@ -81,6 +108,10 @@ public abstract class PathRef implements Comparable<PathRef> {
throw new InvalidModificationException("Invalid delete operation"); throw new InvalidModificationException("Invalid delete operation");
} }
public void convert(ValueConverter valueConverter, Configuration configuration){
throw new InvalidModificationException("Invalid convert operation");
}
@Override @Override
public void delete(Configuration configuration) { public void delete(Configuration configuration) {
throw new InvalidModificationException("Invalid delete operation"); throw new InvalidModificationException("Invalid delete operation");
@ -103,27 +134,45 @@ public abstract class PathRef implements Comparable<PathRef> {
throw new InvalidModificationException("Invalid put operation. $ is not a map"); 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 { private static class ArrayIndexPathRef extends PathRef {
private int index; private int index;
private Object value;
private ArrayIndexPathRef(Object parent, int index) { private ArrayIndexPathRef(Object parent, int index) {
super(parent); super(parent);
this.index = index; this.index = index;
this.value = ((List<Object>) parent).get(index);
} }
public void set(Object newVal, Configuration configuration){ public void set(Object newVal, Configuration configuration){
configuration.jsonProvider().setArrayIndex(parent, index, newVal); 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){ public void delete(Configuration configuration){
configuration.jsonProvider().removeProperty(parent, index); configuration.jsonProvider().removeProperty(parent, value);
} }
public void add(Object value, Configuration configuration){ public void add(Object value, Configuration configuration){
Object target = configuration.jsonProvider().getArrayIndex(parent, index); Object target = configuration.jsonProvider().getArrayIndex(parent, index);
if(target == JsonProvider.UNDEFINED || target == null){ if(targetInvalid(target)){
return; return;
} }
if(configuration.jsonProvider().isArray(target)){ if(configuration.jsonProvider().isArray(target)){
@ -135,7 +184,7 @@ public abstract class PathRef implements Comparable<PathRef> {
public void put(String key, Object value, Configuration configuration){ public void put(String key, Object value, Configuration configuration){
Object target = configuration.jsonProvider().getArrayIndex(parent, index); Object target = configuration.jsonProvider().getArrayIndex(parent, index);
if(target == JsonProvider.UNDEFINED || target == null){ if(targetInvalid(target)){
return; return;
} }
if(configuration.jsonProvider().isMap(target)){ if(configuration.jsonProvider().isMap(target)){
@ -145,6 +194,15 @@ public abstract class PathRef implements Comparable<PathRef> {
} }
} }
@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 @Override
public Object getAccessor() { public Object getAccessor() {
return index; return index;
@ -175,13 +233,20 @@ public abstract class PathRef implements Comparable<PathRef> {
configuration.jsonProvider().setProperty(parent, property, newVal); 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){ public void delete(Configuration configuration){
configuration.jsonProvider().removeProperty(parent, property); configuration.jsonProvider().removeProperty(parent, property);
} }
public void add(Object value, Configuration configuration){ public void add(Object value, Configuration configuration){
Object target = configuration.jsonProvider().getMapValue(parent, property); Object target = configuration.jsonProvider().getMapValue(parent, property);
if(target == JsonProvider.UNDEFINED || target == null){ if(targetInvalid(target)){
return; return;
} }
if(configuration.jsonProvider().isArray(target)){ if(configuration.jsonProvider().isArray(target)){
@ -193,7 +258,7 @@ public abstract class PathRef implements Comparable<PathRef> {
public void put(String key, Object value, Configuration configuration){ public void put(String key, Object value, Configuration configuration){
Object target = configuration.jsonProvider().getMapValue(parent, property); Object target = configuration.jsonProvider().getMapValue(parent, property);
if(target == JsonProvider.UNDEFINED || target == null){ if(targetInvalid(target)){
return; return;
} }
if(configuration.jsonProvider().isMap(target)){ if(configuration.jsonProvider().isMap(target)){
@ -203,6 +268,15 @@ public abstract class PathRef implements Comparable<PathRef> {
} }
} }
@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 @Override
public Object getAccessor() { public Object getAccessor() {
return property; return property;
@ -223,6 +297,12 @@ public abstract class PathRef implements Comparable<PathRef> {
configuration.jsonProvider().setProperty(parent, property, newVal); 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){ public void delete(Configuration configuration){
for (String property : properties) { for (String property : properties) {
@ -237,7 +317,12 @@ public abstract class PathRef implements Comparable<PathRef> {
@Override @Override
public void put(String key, Object newVal, Configuration configuration) { 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 @Override

10
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);
}

8
json-path/src/main/java/com/jayway/jsonpath/spi/json/AbstractJsonProvider.java

@ -99,16 +99,16 @@ public abstract class AbstractJsonProvider implements JsonProvider {
* Removes a value in an object or array * Removes a value in an object or array
* *
* @param obj an array or an object * @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") @SuppressWarnings("unchecked")
public void removeProperty(Object obj, Object key) { public void removeProperty(Object obj, Object key) {
if (isMap(obj)) if (isMap(obj))
((Map) obj).remove(key.toString()); ((Map) obj).remove(key.toString());
else { else {
List list = (List) obj; Collection collection = (Collection) obj;
int index = key instanceof Integer ? (Integer) key : Integer.parseInt(key.toString()); //int index = key instanceof Integer ? (Integer) key : Integer.parseInt(key.toString());
list.remove(index); collection.remove(key);
} }
} }

105
json-path/src/test/java/com/jayway/jsonpath/WriteTest.java

@ -1,11 +1,9 @@
package com.jayway.jsonpath; package com.jayway.jsonpath;
import com.jayway.jsonpath.internal.ValueConverter;
import org.junit.Test; import org.junit.Test;
import java.util.HashMap; import java.util.*;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import static com.jayway.jsonpath.JsonPath.parse; import static com.jayway.jsonpath.JsonPath.parse;
import static java.util.Collections.emptyMap; import static java.util.Collections.emptyMap;
@ -126,6 +124,15 @@ public class WriteTest extends BaseTest {
assertThat(res).containsExactly("reference"); 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<Object> result = documentContext.read(deletePath);
assertThat(result.size()).isEqualTo(0);
}
@Test @Test
public void multi_prop_delete() { public void multi_prop_delete() {
@ -214,6 +221,96 @@ public class WriteTest extends BaseTest {
parse(model).set("$[?(@.a == 'a-val')]", 1); 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<Object> 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<Object> 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<Object> 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<Integer> model = new LinkedList<Integer>();
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");
}
@Test(expected = PathNotFoundException.class)
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<String>)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<Double> doubleResult = parse(JSON_DOCUMENT).read("$..display-price");
for(Double dRes : doubleResult){
assertThat(dRes).isInstanceOf(Double.class);
}
List<String> 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";
}
}
} }

54
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"
}
]
}
}
Loading…
Cancel
Save