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.JsonReader;
import com.jayway.jsonpath.internal.Utils; 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.compiler.Path;
import com.jayway.jsonpath.spi.http.HttpProviderFactory; import com.jayway.jsonpath.spi.http.HttpProviderFactory;
import com.jayway.jsonpath.spi.json.JsonProvider; import com.jayway.jsonpath.spi.json.JsonProvider;
@ -28,6 +29,8 @@ import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.URL; import java.net.URL;
import java.util.List;
import java.util.Map;
import static com.jayway.jsonpath.internal.Utils.*; 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 * Applies this JsonPath to the provided json string
* *
@ -215,6 +275,18 @@ public class JsonPath {
return read(json, Configuration.defaultConfiguration()); 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 * Applies this JsonPath to the provided json string
* *
@ -231,6 +303,18 @@ public class JsonPath {
return read(configuration.getProvider().parse(json), configuration); 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 * 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); 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); this.isDefinite = (Operation.SINGLE_INDEX == operation || Operation.CONTEXT_SIZE == operation);
} }
public List<Integer> getCriteria() {
return criteria;
}
@Override @Override
void evaluate(String currentPath, Object model, EvaluationContextImpl ctx) { void evaluate(String currentPath, Object model, EvaluationContextImpl ctx) {
if(model == null){ if(model == null){
@ -167,4 +171,12 @@ class ArrayPathToken extends PathToken {
boolean isTokenDefinite() { boolean isTokenDefinite() {
return isDefinite; 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.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
class CompiledPath implements Path { public class CompiledPath implements Path {
private static final Logger logger = LoggerFactory.getLogger(CompiledPath.class); 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; this.root = root;
} }
@ -34,6 +37,16 @@ class CompiledPath implements Path {
return root.isPathDefinite(); return root.isPathDefinite();
} }
@Override
public Path clone() {
RootPathToken newRoot = null;
if (root != null) {
newRoot = root.clone();
}
Path path = new CompiledPath(newRoot);
return path;
}
@Override @Override
public String toString() { public String toString() {
return root.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; return configuration;
} }
@Override
public Object getValueResult() {
return valueResult;
}
@Override
public Object getPathResult() {
return pathResult;
}
@Override @Override
public <T> T getValue() { 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() { boolean isTokenDefinite() {
return false; 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; RootPathToken root = null;
PathToken pathToken = null;
/**
* Track previous path token we looped through, for setting previous links.
*/
PathToken previousPathToken = null;
char[] chars = path.toCharArray(); char[] chars = path.toCharArray();
int i = 0; int i = 0;
@ -104,12 +108,20 @@ public class PathCompiler {
i += positions; i += positions;
break; break;
} }
pathToken = PathComponentAnalyzer.analyze(fragment, filterList);
// set the previous link
pathToken.setPrevious(previousPathToken);
if (root == null) { if (root == null) {
root = (RootPathToken) PathComponentAnalyzer.analyze(fragment, filterList); root = (RootPathToken) pathToken;
} else { } else {
root.append(PathComponentAnalyzer.analyze(fragment, filterList)); root.append(pathToken);
} }
previousPathToken = pathToken;
} while (i < chars.length); } while (i < chars.length);
Path pa = new CompiledPath(root); 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; import java.util.List;
abstract class PathToken { public abstract class PathToken {
private PathToken previous;
private PathToken next; private PathToken next;
private Boolean definite = null; 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) { PathToken appendTailToken(PathToken next) {
this.next = next; this.next = next;
return next; return next;
@ -94,14 +111,14 @@ abstract class PathToken {
} }
PathToken next() { public PathToken next() {
if (isLeaf()) { if (isLeaf()) {
throw new IllegalStateException("Current path token is a leaf"); throw new IllegalStateException("Current path token is a leaf");
} }
return next; return next;
} }
boolean isLeaf() { public boolean isLeaf() {
return next == null; return next == null;
} }
@ -154,4 +171,27 @@ abstract class PathToken {
abstract String getPathFragment(); 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; private final List<String> properties;
@ -41,4 +41,12 @@ class PropertyPathToken extends PathToken {
.append(Utils.join(", ", "'", properties)) .append(Utils.join(", ", "'", properties))
.append("]").toString(); .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); private static final Logger logger = LoggerFactory.getLogger(RootPathToken.class);
@ -18,11 +18,23 @@ class RootPathToken extends PathToken /*implements Path*/ {
this.tokenCount = 1; this.tokenCount = 1;
} }
public PathToken getTail() {
return tail;
}
public void setTail(PathToken tail) {
this.tail = tail;
}
@Override @Override
public int getTokenCount() { public int getTokenCount() {
return tokenCount; return tokenCount;
} }
public void setTokenCount(int tokenCount) {
this.tokenCount = tokenCount;
}
public RootPathToken append(PathToken next) { public RootPathToken append(PathToken next) {
this.tail = tail.appendTailToken(next); this.tail = tail.appendTailToken(next);
this.tokenCount++; this.tokenCount++;
@ -63,4 +75,25 @@ class RootPathToken extends PathToken /*implements Path*/ {
boolean isTokenDefinite() { boolean isTokenDefinite() {
return true; 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); 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() { public String getPathFragment() {
return "[*]"; 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(); 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 * 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} * 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; package com.jayway.jsonpath.spi.compiler;
import com.jayway.jsonpath.Configuration; 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); EvaluationContext evaluate(Object model, Configuration configuration);
public RootPathToken getRoot();
boolean isDefinite(); boolean isDefinite();
public Path clone();
} }

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

@ -1,11 +1,13 @@
package com.jayway.jsonpath; package com.jayway.jsonpath;
import com.jayway.jsonpath.internal.spi.compiler.PathCompiler; 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.spi.json.JsonProviderFactory;
import com.jayway.jsonpath.util.ScriptEngineJsonPath; import com.jayway.jsonpath.util.ScriptEngineJsonPath;
import org.assertj.core.api.Assertions; import org.assertj.core.api.Assertions;
import org.junit.Test; import org.junit.Test;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -192,6 +194,70 @@ public class JsonPathTest {
assertEquals(2, result.values().size()); 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 @Test
public void read_store_book_1() throws Exception { public void read_store_book_1() throws Exception {

Loading…
Cancel
Save