Browse Source

support writing values

pull/41/head
Anders D. Johnson 11 years ago
parent
commit
08b77de4f4
  1. 86
      json-path/src/main/java/com/jayway/jsonpath/JsonPath.java
  2. 14
      json-path/src/main/java/com/jayway/jsonpath/internal/spi/compiler/ArrayPathToken.java
  3. 19
      json-path/src/main/java/com/jayway/jsonpath/internal/spi/compiler/CompiledPath.java
  4. 9
      json-path/src/main/java/com/jayway/jsonpath/internal/spi/compiler/EvaluationContextImpl.java
  5. 8
      json-path/src/main/java/com/jayway/jsonpath/internal/spi/compiler/FilterPathToken.java
  6. 18
      json-path/src/main/java/com/jayway/jsonpath/internal/spi/compiler/PathCompiler.java
  7. 46
      json-path/src/main/java/com/jayway/jsonpath/internal/spi/compiler/PathToken.java
  8. 10
      json-path/src/main/java/com/jayway/jsonpath/internal/spi/compiler/PropertyPathToken.java
  9. 35
      json-path/src/main/java/com/jayway/jsonpath/internal/spi/compiler/RootPathToken.java
  10. 8
      json-path/src/main/java/com/jayway/jsonpath/internal/spi/compiler/ScanPathToken.java
  11. 8
      json-path/src/main/java/com/jayway/jsonpath/internal/spi/compiler/WildcardPathToken.java
  12. 4
      json-path/src/main/java/com/jayway/jsonpath/spi/compiler/EvaluationContext.java
  13. 5
      json-path/src/main/java/com/jayway/jsonpath/spi/compiler/Path.java
  14. 66
      json-path/src/test/java/com/jayway/jsonpath/JsonPathTest.java

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

@ -17,7 +17,8 @@ package com.jayway.jsonpath;
import com.jayway.jsonpath.internal.JsonReader;
import com.jayway.jsonpath.internal.Utils;
import com.jayway.jsonpath.internal.spi.compiler.PathCompiler;
import com.jayway.jsonpath.internal.spi.compiler.*;
import com.jayway.jsonpath.spi.compiler.EvaluationContext;
import com.jayway.jsonpath.spi.compiler.Path;
import com.jayway.jsonpath.spi.http.HttpProviderFactory;
import com.jayway.jsonpath.spi.json.JsonProvider;
@ -28,6 +29,8 @@ import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.List;
import java.util.Map;
import static com.jayway.jsonpath.internal.Utils.*;
@ -203,6 +206,63 @@ public class JsonPath {
}
/**
* Writes the provided value at this path to the provided json document.
* Note that the document must be identified as either a List or Map by
* the {@link JsonProvider}
*
* @param jsonObject a container Object
* @param value value to set
* @param configuration configuration to use
* @return a modified container object
*/
public Map write(Object jsonObject, Object value, Configuration configuration) {
// new configuration that will always return list
Configuration.ConfigurationBuilder builder = new Configuration.ConfigurationBuilder();
Configuration readConfig = builder
.jsonProvider(configuration.getProvider())
.options(Option.ALWAYS_RETURN_LIST)
.build();
// clone the path for manipulation to prevent modifications by reference
Path clonedPath = path.clone();
RootPathToken clonedRoot = clonedPath.getRoot();
PathToken tail = clonedRoot.getTail();
PathToken parent = tail.getPrevious();
parent.setNext(null);
// get results for parent paths
EvaluationContext res = clonedPath.evaluate(jsonObject, readConfig);
for (Object valueResult : (List<Object>) res.getValueResult()) {
Class clazz = valueResult.getClass();
// set index value in array
if (List.class.isAssignableFrom(clazz)) {
ArrayPathToken arrayPathToken = (ArrayPathToken) tail;
List<Integer> criteria = arrayPathToken.getCriteria();
Integer index = criteria.get(0);
List list = (List) valueResult;
list.set(index, value);
}
// set property value on object
else if (Map.class.isAssignableFrom(clazz)) {
PropertyPathToken propertyPathToken = (PropertyPathToken) tail;
List<String> properties = propertyPathToken.getProperties();
String key = properties.get(0);
Map map = (Map) valueResult;
map.put(key, value);
}
}
return (Map) jsonObject;
}
/**
* Applies this JsonPath to the provided json string
*
@ -215,6 +275,18 @@ public class JsonPath {
return read(json, Configuration.defaultConfiguration());
}
/**
* Writes the provided value at this path to the provided json string.
*
* @param json a json string
* @param value value to set at path
* @return a modified container object
*/
public Map write(String json, Object value) {
Configuration configuration = Configuration.defaultConfiguration();
return write(json, value, configuration);
}
/**
* Applies this JsonPath to the provided json string
*
@ -231,6 +303,18 @@ public class JsonPath {
return read(configuration.getProvider().parse(json), configuration);
}
/**
*
* @param json a json string
* @param value value to set at path
* @param configuration
* @return a modified container object
*/
public Map write(String json, Object value, Configuration configuration) {
Object jsonObject = configuration.getProvider().parse(json);
return write(jsonObject, value, configuration);
}
/**
* Applies this JsonPath to the provided json URL
*

14
json-path/src/main/java/com/jayway/jsonpath/internal/spi/compiler/ArrayPathToken.java

@ -13,7 +13,7 @@ import static java.lang.String.format;
/**
*
*/
class ArrayPathToken extends PathToken {
public class ArrayPathToken extends PathToken {
private static final Logger logger = LoggerFactory.getLogger(ArrayPathToken.class);
@ -36,6 +36,10 @@ class ArrayPathToken extends PathToken {
this.isDefinite = (Operation.SINGLE_INDEX == operation || Operation.CONTEXT_SIZE == operation);
}
public List<Integer> getCriteria() {
return criteria;
}
@Override
void evaluate(String currentPath, Object model, EvaluationContextImpl ctx) {
if(model == null){
@ -167,4 +171,12 @@ class ArrayPathToken extends PathToken {
boolean isTokenDefinite() {
return isDefinite;
}
@Override
public ArrayPathToken clone() {
ArrayPathToken pathToken = new ArrayPathToken(criteria, operation);
cloneTo(pathToken);
return pathToken;
}
}

19
json-path/src/main/java/com/jayway/jsonpath/internal/spi/compiler/CompiledPath.java

@ -6,14 +6,17 @@ import com.jayway.jsonpath.spi.compiler.Path;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class CompiledPath implements Path {
public class CompiledPath implements Path {
private static final Logger logger = LoggerFactory.getLogger(CompiledPath.class);
private final PathToken root;
private final RootPathToken root;
public RootPathToken getRoot() {
return root;
}
public CompiledPath(PathToken root) {
public CompiledPath(RootPathToken root) {
this.root = root;
}
@ -34,6 +37,16 @@ class CompiledPath implements Path {
return root.isPathDefinite();
}
@Override
public Path clone() {
RootPathToken newRoot = null;
if (root != null) {
newRoot = root.clone();
}
Path path = new CompiledPath(newRoot);
return path;
}
@Override
public String toString() {
return root.toString();

9
json-path/src/main/java/com/jayway/jsonpath/internal/spi/compiler/EvaluationContextImpl.java

@ -49,6 +49,15 @@ class EvaluationContextImpl implements EvaluationContext {
return configuration;
}
@Override
public Object getValueResult() {
return valueResult;
}
@Override
public Object getPathResult() {
return pathResult;
}
@Override
public <T> T getValue() {

8
json-path/src/main/java/com/jayway/jsonpath/internal/spi/compiler/FilterPathToken.java

@ -70,4 +70,12 @@ class FilterPathToken extends PathToken {
boolean isTokenDefinite() {
return false;
}
@Override
public FilterPathToken clone() {
FilterPathToken pathToken = new FilterPathToken(filters);
cloneTo(pathToken);
return pathToken;
}
}

18
json-path/src/main/java/com/jayway/jsonpath/internal/spi/compiler/PathCompiler.java

@ -50,7 +50,11 @@ public class PathCompiler {
}
RootPathToken root = null;
PathToken pathToken = null;
/**
* Track previous path token we looped through, for setting previous links.
*/
PathToken previousPathToken = null;
char[] chars = path.toCharArray();
int i = 0;
@ -104,12 +108,20 @@ public class PathCompiler {
i += positions;
break;
}
pathToken = PathComponentAnalyzer.analyze(fragment, filterList);
// set the previous link
pathToken.setPrevious(previousPathToken);
if (root == null) {
root = (RootPathToken) PathComponentAnalyzer.analyze(fragment, filterList);
root = (RootPathToken) pathToken;
} else {
root.append(PathComponentAnalyzer.analyze(fragment, filterList));
root.append(pathToken);
}
previousPathToken = pathToken;
} while (i < chars.length);
Path pa = new CompiledPath(root);

46
json-path/src/main/java/com/jayway/jsonpath/internal/spi/compiler/PathToken.java

@ -8,11 +8,28 @@ import com.jayway.jsonpath.spi.json.JsonProvider;
import java.util.List;
abstract class PathToken {
public abstract class PathToken {
private PathToken previous;
private PathToken next;
private Boolean definite = null;
public PathToken getPrevious() {
return previous;
}
public void setPrevious(PathToken previous) {
this.previous = previous;
}
public void setNext(PathToken next) {
this.next = next;
}
public void setDefinte(Boolean definite) {
this.definite = definite;
}
PathToken appendTailToken(PathToken next) {
this.next = next;
return next;
@ -94,14 +111,14 @@ abstract class PathToken {
}
PathToken next() {
public PathToken next() {
if (isLeaf()) {
throw new IllegalStateException("Current path token is a leaf");
}
return next;
}
boolean isLeaf() {
public boolean isLeaf() {
return next == null;
}
@ -154,4 +171,27 @@ abstract class PathToken {
abstract String getPathFragment();
@Override
abstract public PathToken clone();
/**
* Deep clone this path's fields to the provided path.
* While next links are cloned, previous links are only updated, to prevent infinite recursion.
* Cannot use standard clone and super calls for sub-classes due to abstract modifier.
*
* @param pathToken
*/
public void cloneTo(PathToken pathToken) {
PathToken newNext;
if (next != null) {
newNext = next.clone();
newNext.setPrevious(pathToken);
}
else {
newNext = null;
}
pathToken.setNext(newNext);
pathToken.setDefinte(definite);
}
}

10
json-path/src/main/java/com/jayway/jsonpath/internal/spi/compiler/PropertyPathToken.java

@ -8,7 +8,7 @@ import java.util.List;
/**
*
*/
class PropertyPathToken extends PathToken {
public class PropertyPathToken extends PathToken {
private final List<String> properties;
@ -41,4 +41,12 @@ class PropertyPathToken extends PathToken {
.append(Utils.join(", ", "'", properties))
.append("]").toString();
}
@Override
public PropertyPathToken clone() {
PropertyPathToken pathToken = new PropertyPathToken(properties);
cloneTo(pathToken);
return pathToken;
}
}

35
json-path/src/main/java/com/jayway/jsonpath/internal/spi/compiler/RootPathToken.java

@ -6,7 +6,7 @@ import org.slf4j.LoggerFactory;
/**
*
*/
class RootPathToken extends PathToken /*implements Path*/ {
public class RootPathToken extends PathToken /*implements Path*/ {
private static final Logger logger = LoggerFactory.getLogger(RootPathToken.class);
@ -18,11 +18,23 @@ class RootPathToken extends PathToken /*implements Path*/ {
this.tokenCount = 1;
}
public PathToken getTail() {
return tail;
}
public void setTail(PathToken tail) {
this.tail = tail;
}
@Override
public int getTokenCount() {
return tokenCount;
}
public void setTokenCount(int tokenCount) {
this.tokenCount = tokenCount;
}
public RootPathToken append(PathToken next) {
this.tail = tail.appendTailToken(next);
this.tokenCount++;
@ -63,4 +75,25 @@ class RootPathToken extends PathToken /*implements Path*/ {
boolean isTokenDefinite() {
return true;
}
public PathToken walkToTail() {
PathToken next = this;
while (next != null && ! next.isLeaf()) {
next = next.next();
}
return next;
}
@Override
public RootPathToken clone() {
RootPathToken pathToken = new RootPathToken();
// deep clone 'next' path
cloneTo(pathToken);
// get new tail clone
PathToken tail = pathToken.walkToTail();
pathToken.setTail(tail);
pathToken.setTokenCount(tokenCount);
return pathToken;
}
}

8
json-path/src/main/java/com/jayway/jsonpath/internal/spi/compiler/ScanPathToken.java

@ -171,4 +171,12 @@ class ScanPathToken extends PathToken {
boolean matches(Object model);
}
@Override
public ScanPathToken clone() {
ScanPathToken pathToken = new ScanPathToken();
cloneTo(pathToken);
return pathToken;
}
}

8
json-path/src/main/java/com/jayway/jsonpath/internal/spi/compiler/WildcardPathToken.java

@ -30,4 +30,12 @@ class WildcardPathToken extends PathToken {
public String getPathFragment() {
return "[*]";
}
@Override
public ScanPathToken clone() {
ScanPathToken pathToken = new ScanPathToken();
cloneTo(pathToken);
return pathToken;
}
}

4
json-path/src/main/java/com/jayway/jsonpath/spi/compiler/EvaluationContext.java

@ -12,6 +12,10 @@ public interface EvaluationContext {
*/
Configuration configuration();
Object getValueResult();
Object getPathResult();
/**
* This method does not adhere to configuration settings. It will return a single object (not wrapped in a List) even if the
* configuration contains the {@link com.jayway.jsonpath.Option#ALWAYS_RETURN_LIST}

5
json-path/src/main/java/com/jayway/jsonpath/spi/compiler/Path.java

@ -1,6 +1,7 @@
package com.jayway.jsonpath.spi.compiler;
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.internal.spi.compiler.RootPathToken;
/**
*
@ -9,6 +10,10 @@ public interface Path {
EvaluationContext evaluate(Object model, Configuration configuration);
public RootPathToken getRoot();
boolean isDefinite();
public Path clone();
}

66
json-path/src/test/java/com/jayway/jsonpath/JsonPathTest.java

@ -1,11 +1,13 @@
package com.jayway.jsonpath;
import com.jayway.jsonpath.internal.spi.compiler.PathCompiler;
import com.jayway.jsonpath.spi.json.JsonProvider;
import com.jayway.jsonpath.spi.json.JsonProviderFactory;
import com.jayway.jsonpath.util.ScriptEngineJsonPath;
import org.assertj.core.api.Assertions;
import org.junit.Test;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -192,6 +194,70 @@ public class JsonPathTest {
assertEquals(2, result.values().size());
}
@Test
public void write_store_book_author() throws Exception {
JsonPath path = JsonPath.compile("$.store.book[0].author");
String writeAuthor = "Douglas Adams";
Map jsonMap = path.write(DOCUMENT, writeAuthor);
JsonProvider provider = JsonProviderFactory.createProvider();
String json = provider.toJson(jsonMap);
String readAuthor = path.read(json);
assertEquals(writeAuthor, readAuthor);
}
@Test
public void write_store_book_authors() throws Exception {
JsonPath path = JsonPath.compile("$.store.book[0,1].author");
String writeAuthor = "Douglas Adams";
Map jsonMap = path.write(DOCUMENT, writeAuthor);
JsonProvider provider = JsonProviderFactory.createProvider();
String json = provider.toJson(jsonMap);
List<String> readAuthors = path.read(json);
for (String readAuthor : readAuthors) {
assertEquals(writeAuthor, readAuthor);
}
}
@Test
public void write_store_book_0_shallow() throws Exception {
JsonPath path = JsonPath.compile("$.store.book[0]");
String writeBook = "Book";
Map jsonMap = path.write(DOCUMENT, writeBook);
JsonProvider provider = JsonProviderFactory.createProvider();
String json = provider.toJson(jsonMap);
String readBook = path.read(json);
assertEquals(writeBook, readBook);
}
@Test
public void write_store_book_0_deep() throws Exception {
JsonPath path = JsonPath.compile("$.store.book[0]");
HashMap<String, Object> writeBook = new HashMap<String, Object>();
String writeBookAuthor = "New Author";
writeBook.put("author", writeBookAuthor);
Map jsonMap = path.write(DOCUMENT, writeBook);
JsonProvider provider = JsonProviderFactory.createProvider();
String json = provider.toJson(jsonMap);
JsonPath readPath = JsonPath.compile("$.store.book[0].author");
String readBookAuthor = readPath.read(json);
assertEquals(writeBookAuthor, readBookAuthor);
}
@Test
public void read_store_book_1() throws Exception {

Loading…
Cancel
Save