I have the following code:
public JSONObject addProductToOrder(int orderId, int productId, String name, double price) {
JSONObject json = new JSONObject();
try {
json.put("orderId", orderId);
json.put("productId", productId);
json.put("name", name);
json.put("price", price);
} catch (JSONException e) {
Debugger.out(e);
}
return this.dataSource.write("/orderItems/addproductToOrder", json);
}
I want to test so using Mockito and I have done the following:
public void getOrder() throws JSONException {
JSONObject json = new JSONObject("{result: 'OK', error: 0}");
doReturn(json)
.when(this.clientSpy) // this.clientSpy = Mockito.spy(client);
.post("/orderItems/addProductToOrder", new JSONObject("{productId: 1, orderId: 1, price: 15.99, name: 'Product 1'}"));
OrderItem orderItem = new OrderItem(this.api);
assertEquals("OK", orderItem.addProductToOrder(1, 1, "Product 1", 15.99).get("result"));
}
The way I understand things, my doReturn() is not triggered because new JSONObject() !== new JSONObject().
Is there a way to make it not compare the objects, but instead just the contents?
What happens here is that Mockito is calling equals() to compare the object that you provided to the specification to the one that is used for the actual method call.
Basically, there are three options:
if you just provide an object, equals() is used to compare
then you have a variety of other ArgumentMatchers, such as any() or isNull(). Please note: when using such matchers, all arguments must be matched, you can't do: foo(any(), 5) for example.
if that doesn't do: you can also use an ArgumentCaptor. Meaning: instead of having Mockito compare objects, you simply record the object that was passed to the mock. And then you add another step in your test case where you check the captured argument as required.
This argThat function can help you.
private static String eqJson(String expectedJson) {
return argThat(argument -> {
try {
JSONAssert.assertEquals(expectedJson, argument, true);
return true;
} catch (JSONException e) {
return false;
}
});
}
Then in your test
// import
import org.skyscreamer.jsonassert.JSONAssert;
import static org.mockito.ArgumentMatchers.argThat;
// then
then(mock).should(times(1)).method(eqJson(expectedJson));
Related
I need to get parameters from DialogFlow to my Android app.
I tried using getQueryResult().getParameters().getFieldsMap()
but the result is the following.
{type=list_value {
values {
string_value: "pizza"
}
}
, ristorante=string_value: ""
}
I would like to get just the string value "pizza" and not the entire FieldMap.
I have already seen this topic, but it didn't help me, because I don't know what protobuf is and seems a bit complicated.
Is there a simple way to get a parameter's value?
I see two possibilities:
Try to access the Map values directly.
The getFieldsMap() method returns a java.util.Map class. You can try to retrieve the values by getting first a collection of Values, then iterate:
Collection colletion = <Detect_Intent_Object>.getQueryResult().getParameters().getFieldsMap().values():
for (iterable_type iterable_element : collection)
From my humble point of view the bucle is necesary because there could be more than one parameter.
Transform the protobuf response into a json and access the values.
Sample code:
import com.google.protobuf.util.JsonFormat;
String jsonString = JsonFormat.printToString(<Detect_Intent_Object>.getQueryResult().getParameters());
// Then use a json parser to obtain the values
import org.json.*;
JSONObject obj = new JSONObject(jsonString);
JSONArray jsonnames = obj.names();
Method names() will let you know the string names you want to access.
If you use Dialogflowv2
public String getParameter(GoogleCloudDialogflowV2WebhookRequest request, String parameterName) {
try {
GoogleCloudDialogflowV2QueryResult queryResult = request.getQueryResult();
Map<String, Object> parameters = queryResult.getParameters();
String parameter = (String) parameters.get(parameterName);
if(parameter != null && !parameter.equals("")) {
return parameter;
}
} catch (ClassCastException e) {
logger.error("Error");
}
return null;
}
If you use GoogleActions
public String getParameter(ActionRequest request, String parameterName) {
try {
Map<String, Object> parameters = request.getWebhookRequest().getQueryResult().getParameters();
String parameter = (String) parameters.get(parameterName);
if(parameter != null && !parameter.equals("")) {
return parameter;
}
} catch (ClassCastException e) {
logger.error("Error");
}
return null;
}
After doing lots of research, I didn't find the answer for this question in JUnits in java.
What I want to do is: To call some method on object returned by when().thenReturn(object) call.
e.g.:
public boolean checkUpdate(String str, String endStr){
GetEndpointRequest geaReq = new
GetEndpointRequest().withEndpointArn(endpointArn);
GetEndpointResult geaRes = amazonSNS.getEndpointAttributes(geaReq);
return !geaRes.getAttributes().get("Token").equals(token) || !geaRes.getAttributes().get("Enabled").equalsIgnoreCase("true");
}
And here is the Test method:
import com.amazonaws.services.sns.model.GetEndpointAttributesRequest;
import com.amazonaws.services.sns.model.GetEndpointAttributesResult;
#Tested
AmazonSNSRegistrationService service= new AmazonSNSRegistrationService();
service.amazonSNS = mock(AmazonSNS.class);
#Test
public void checkUpdateTest(){
String pushToken = "dxbv1fwJYIo";
String strToken = "";
String strEnabled = "";
String endPointArn = "";
Map<String, String> jsonBody = new HashMap<String, String>();
jsonBody.put("Token", "");
jsonBody.put("enabled", "");
GetEndpointAttributesRequest getEndpointReq =mock(GetEndpointAttributesRequest.class);
GetEndpointAttributesResult getEndpointRes =mock(GetEndpointAttributesResult.class);
getEndpointRes.setAttributes(jsonBody);
when(service.amazonSNS.getEndpointAttributes(getEndpointReq)).thenReturn(getEndpointRes);
when(getEndpointRes.getAttributes()).thenReturn(jsonBody);
when(getEndpointRes.getAttributes().get(strToken)).thenReturn("");
when(getEndpointRes.getAttributes().get(strEnabled)).thenReturn("");
amazonSNSRegistrationService.checkUpdate(pushToken, endPointArn);
}
I'm getting NullPointerException in checkUpdate() method on line -- "return !geaRes.getAttributes().get("Token").equals(token)".
As, geaRes is null.
How to solve this?
Adding another simple example , where I face this issue:
AmazonSNSRegistrationSerice.java:
public boolean deletePlatformApplicationArn(String deviceId, String appId){
boolean isArnDeleted = false;
try {
DeleteEndpointRequest deleteEndpointReq = new DeleteEndpointRequest().withEndpointArn(appId);
DeleteEndpointResult result = amazonSNS.deleteEndpoint(deleteEndpointReq);
if (result.getSdkHttpMetadata().getHttpStatusCode() == HttpStatus.SC_OK) {
AWSUtil.deleteArnEndpoint(deviceId, appId);
isArnDeleted = true;
}
} catch (Exception e) {
ErrorLogEventHelper.logErrorEvent(this.getClass().getName(), "Exception while deleting AWS ARN (endpoint)" + e.getMessage(), "deletePlatformApplicationArn", e, ErrorLogEvent.ERROR_SEVERITY);
}
return isArnDeleted;
}
AmazonSNSRegistrationSericeTest.java:
#Test
public void deletePlatformApplicationArnTest(){
String appId = "arn:aws:sns";
String deviceId = "dev_1";
DeleteEndpointRequest deleteEndpointReq = mock(DeleteEndpointRequest.class);
DeleteEndpointResult result = mock(DeleteEndpointResult.class);
when(amazonSNSRegistrationService.amazonSNS.deleteEndpoint(deleteEndpointReq)).thenReturn(result);
SdkHttpMetadata metadata = mock(SdkHttpMetadata.class);
when(result.getSdkHttpMetadata()).thenReturn(metadata);
when(result.getSdkHttpMetadata().getHttpStatusCode()).thenReturn(HttpStatus.SC_OK);
amazonSNSRegistrationService.deletePlatformApplicationArn(deviceId, appId);
}
Again the result object in if(result.getSdkHttpMetadata()) is coming NULL.
So first of all, you're trying to get an empty value out of your JsonMap:
#Test
public void checkUpdateTest(){
String strToken = "";
.
.
.
when(getEndpointRes.getAttributes().get(strToken)).thenReturn("");
// so what you ask here is this:
when(getEndpointRes.getAttributes().get("")).thenReturn("");
// this should probably be "Token"
when(getEndpointRes.getAttributes().get("Token")).thenReturn("");
}
the next thing is, the JsonMap is a real object and not a mock so you don't need to call when(getEndpointRes.getAttributes().get(strToken)).thenReturn(""); as your jsonmap is going to return the right value.
then, this call is not needed as your getEndpointRes is a mock. That's why you use the when().thenX() syntax
getEndpointRes.setAttributes(jsonBody);
To resolve your NPE problem try the following:
// imports...
#Tested
AmazonSNSRegistrationService service= new AmazonSNSRegistrationService();
#Test
public void checkUpdateTest(){
// directly mock the amazonsns here
AmazonSNS amazonSNS = mock(AmazonSNS.class);
service.amazonSNS = amazonSNS;
String pushToken = "dxbv1fwJYIo";
String endPointArn = "";
// maybe mock them aswell
Map<String, String> jsonBody = new HashMap<String, String>();
jsonBody.put("Token", "");
jsonBody.put("enabled", "");
// deleted the mocked request. You don't need it in this case as your service contructs it by itself
GetEndpointAttributesResultgetEndpointRes getEndpointRes = mock(GetEndpointAttributesResult.class);
getEndpointRes.setAttributes(jsonBody);
// directly use the mocked object and react on any() as getEntpointReq never will be present in your service!
when(amazonSNS.getEndpointAttributes(any())).thenReturn(getEndpointRes);
when(getEndpointRes.getAttributes()).thenReturn(jsonBody);
amazonSNSRegistrationService.checkUpdate(pushToken, endPointArn);
// do some further assertments
}
It seems that you try to mock things that you don't need to mock and mock things that are generated in your service. If you ever encounter a NPE in the classes you are trying to test you should debug your test properly. In your case the mocking seems incorrect and therefore the real object calls result in NPEs
In your second example it seems to be the same issue. you try to return something from an mock, that is probably created inside your amazonSNSRegistrationService. How should your mocked deleteEndpointReq return any value if it is not part of the class? You simple create the mock, but it is not the same object that is created inside of you service!
And after all you should add some assertions. The tests I see here only fail if the tested class fails with an error like in your case. But some basic assertions are missing. I personally use the AAA pattern for unit tests.
I want to compare two JSON strings which is a huge hierarchy and want to know where they differ in values. But some values are generated at runtime and are dynamic. I want to ignore those particular nodes from my comparison.
I am currently using JSONAssert from org.SkyScreamer to do the comparison. It gives me nice console output but does not ignore any attributes.
for ex.
java.lang.AssertionError messageHeader.sentTime
expected:null
got:09082016 18:49:41.123
Now this comes dynamic and should be ignored. Something like
JSONAssert.assertEquals(expectedJSONString, actualJSONString,JSONCompareMode, *list of attributes to be ignored*)
It would be great if someone suggests a solution in JSONAssert. However other ways are also welcome.
You can use Customization for this. For example, if you need to ignore a top-level attribute named "timestamp" use:
JSONAssert.assertEquals(expectedResponseBody, responseBody,
new CustomComparator(JSONCompareMode.LENIENT,
new Customization("timestamp", (o1, o2) -> true)));
It's also possible to use path expressions like "entry.id". In your Customization you can use whatever method you like to compare the two values. The example above always returns true, no matter what the expected value and the actual value are. You could do more complicated stuff there if you need to.
It is perfectly fine to ignore that values of multiple attributes, for example:
#Test
public void ignoringMultipleAttributesWorks() throws JSONException {
String expected = "{\"timestamp\":1234567, \"a\":5, \"b\":3 }";
String actual = "{\"timestamp\":987654, \"a\":1, \"b\":3 }";
JSONAssert.assertEquals(expected, actual,
new CustomComparator(JSONCompareMode.LENIENT,
new Customization("timestamp", (o1, o2) -> true),
new Customization("a", (o1, o2) -> true)
));
}
There is one caveat when using Customizations: The attribute whose value is to be compared in a custom way has to be present in the actual JSON. If you want the comparison to succeed even if the attribute is not present at all you would have to override CustomComparator for example like this:
#Test
public void extendingCustomComparatorToAllowToCompletelyIgnoreCertainAttributes() throws JSONException {
// AttributeIgnoringComparator completely ignores some of the expected attributes
class AttributeIgnoringComparator extends CustomComparator{
private final Set<String> attributesToIgnore;
private AttributeIgnoringComparator(JSONCompareMode mode, Set<String> attributesToIgnore, Customization... customizations) {
super(mode, customizations);
this.attributesToIgnore = attributesToIgnore;
}
protected void checkJsonObjectKeysExpectedInActual(String prefix, JSONObject expected, JSONObject actual, JSONCompareResult result) throws JSONException {
Set<String> expectedKeys = getKeys(expected);
expectedKeys.removeAll(attributesToIgnore);
for (String key : expectedKeys) {
Object expectedValue = expected.get(key);
if (actual.has(key)) {
Object actualValue = actual.get(key);
compareValues(qualify(prefix, key), expectedValue, actualValue, result);
} else {
result.missing(prefix, key);
}
}
}
}
String expected = "{\"timestamp\":1234567, \"a\":5}";
String actual = "{\"a\":5}";
JSONAssert.assertEquals(expected, actual,
new AttributeIgnoringComparator(JSONCompareMode.LENIENT,
new HashSet<>(Arrays.asList("timestamp")))
);
}
(With this approach you still could use Customizations to compare other attributes' values in the way you want.)
you can use JsonUnit It has the functionality that you are looking for we can ignore fields, paths, and values that are null etc. Check it out for more info. As for the example, you can ignore a path like this
assertJsonEquals(
"{\"root\":{\"test\":1, \"ignored\": 2}}",
"{\"root\":{\"test\":1, \"ignored\": 1}}",
whenIgnoringPaths("root.ignored")
);
Sometimes you need to ignore certain values when comparing. It is possible to use ${json-unit.ignore} placeholder like this
assertJsonEquals("{\"test\":\"${json-unit.ignore}\"}",
"{\n\"test\": {\"object\" : {\"another\" : 1}}}");
First of all there is open issue for it.
In my tests I compare json from controller with actual object with help of JsonUtil class for serialization/deserialization:
public class JsonUtil {
public static <T> List<T> readValues(String json, Class<T> clazz) {
ObjectReader reader = getMapper().readerFor(clazz);
try {
return reader.<T>readValues(json).readAll();
} catch (IOException e) {
throw new IllegalArgumentException("Invalid read array from JSON:\n'" + json + "'", e);
}
}
public static <T> T readValue(String json, Class<T> clazz) {
try {
return getMapper().readValue(json, clazz);
} catch (IOException e) {
throw new IllegalArgumentException("Invalid read from JSON:\n'" + json + "'", e);
}
}
public static <T> String writeValue(T obj) {
try {
return getMapper().writeValueAsString(obj);
} catch (JsonProcessingException e) {
throw new IllegalStateException("Invalid write to JSON:\n'" + obj + "'", e);
}
}
To ignore specific object field I've add new method:
public static <T> String writeIgnoreProps(T obj, String... ignoreProps) {
try {
Map<String, Object> map = getMapper().convertValue(obj, new TypeReference<Map<String, Object>>() {});
for (String prop : ignoreProps) {
map.remove(prop);
}
return getMapper().writeValueAsString(map);
} catch (JsonProcessingException e) {
throw new IllegalStateException("Invalid write to JSON:\n'" + obj + "'", e);
}
}
and my assert in test now look like this:
mockMvc.perform(get(REST_URL))
.andExpect(status().isOk())
.andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
.andExpect(content().json(JsonUtil.writeIgnoreProps(USER, "registered")))
Thank you #dknaus for the detailed answer. Although this solution will not work in STRICT mode and checkJsonObjectKeysExpectedInActual method code needs to be replaced by following code [As suggested by #tk-gospodinov]:
for (String attribute : attributesToIgnore) {
expected.remove(attribute);
super.checkJsonObjectKeysExpectedInActual(prefix, expected, actual, result);
}
I have the Java Method getStatusAndAnnotation in TestListener class as below:
public void getStatusAndAnnotation(ITestResult result) {
Map<Object, Object> map = new HashMap<Object, Object>();
Method method = result.getMethod().getConstructorOrMethod().getMethod();
Annotation annotation = TestListener.class.getAnnotation(TestInfo.class);
int status = 0;
try {
TestInfo testinfo = (TestInfo) annotation;
if (annotation!=null) {
for (String testId: testinfo.id()) {
map.put("id",testId.substring(1));
switch (status) {
case ITestResult.SUCCESS:
map.put("result", STATUS.PASSED.getValue());
case ITestResult.FAILURE:
map.put("result", STATUS.AUTO_FAIL.getValue());
default:
map.put("result", STATUS.UNTESTED.getValue());
}
ResultCollector.addTestResult(map);
}
} catch (SecurityException e) {
TestLogger.logInfo("Failed to find the annotation and the status of the test " + method);
e.printStackTrace();
}
}
Here what I am doing is getting the Java TestNG method, its annotation and the status of the tests such as Pass, Fail, etc. Putting them in map one by one as you can see in code above - calls of map.put("id",testId.substring(1)); and
map.put("result", STATUS.UNTESTED.getValue());
Finally I am calling the method addTestResult() method from my ResultCollector class that will hold all these maps.
However, I see people suggesting me to create an object rather than directly putting in map as I am just storing 2 values - id and status for each test.
How do I use object instead of map and what is the better way in Java to accomplish this?
Inshort : I am trying to find some api that could just change the value by taking first parameter as jsonString , second parameter as JSONPath and third will be new value of that parameter. But, all I found is this..
https://code.google.com/p/json-path/
This api allows me to find any value in JSON String. But, I am not finding easy way to update the value of any key. For example, Here is a book.json.
{
"store":{
"book":[
{
"category":"reference",
"author":"Nigel Rees",
"title":"Sayings of the Century",
"price":8.95
},
{
"category":"fiction",
"author":"Evelyn Waugh",
"title":"Sword of Honour",
"price":12.99,
"isbn":"0-553-21311-3"
}
],
"bicycle":{
"color":"red",
"price":19.95
}
}
}
I can access color of bicycle by doing this.
String bicycleColor = JsonPath.read(json, "$.store.bicycle.color");
But I am looking for a method in JsonPath or other api some thing like this
JsonPath.changeNodeValue(json, "$.store.bicycle.color", "green");
String bicycleColor = JsonPath.read(json, "$.store.bicycle.color");
System.out.println(bicycleColor); // This should print "green" now.
I am excluding these options,
Create a new JSON String.
Create a JSON Object to deal with changing value and convert it back to jsonstring
Reason: I have about 500 different requests for different types of service which return different json structure. So, I do not want to manually create new JSON string always. Because, IDs are dynamic in json structure.
Any idea or direction is much appreciated.
Updating this question with following answer.
Copy MutableJson.java.
copy this little snippet and modify as per you need.
private static void updateJsonValue() {
JSONParser parser = new JSONParser();
JSONObject jsonObject = new JSONObject();
FileReader reader = null;
try {
File jsonFile = new File("path to book.json");
reader = new FileReader(jsonFile);
jsonObject = (JSONObject) parser.parse(reader);
} catch (Exception ex) {
System.out.println(ex.getLocalizedMessage());
}
Map<String, Object> userData = null;
try {
userData = new ObjectMapper().readValue(jsonObject.toJSONString(), Map.class);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
MutableJson json = new MutableJson(userData);
System.out.println("Before:\t" + json.map());
json.update("$.store.book[0].author", "jigish");
json.update("$.store.book[1].category", "action");
System.out.println("After:\t" + json.map().toString());
}
Use these libraries.
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.codehaus.jackson.map.ObjectMapper;
The thing is that the functionality you want is already an undocumented feature of JsonPath. Example using your json structure:
String json = "{ \"store\":{ \"book\":[ { \"category\":\"reference\", \"author\":\"Nigel Rees\", \"title\":\"Sayings of the Century\", \"price\":8.95 }, { \"category\":\"fiction\", \"author\":\"Evelyn Waugh\", \"title\":\"Sword of Honour\", \"price\":12.99, \"isbn\":\"0-553-21311-3\" } ], \"bicycle\":{ \"color\":\"red\", \"price\":19.95 } } }";
DocumentContext doc = JsonPath.parse(json).
set("$.store.bicycle.color", "green").
set("$.store.book[0].price", 9.5);
String newJson = new Gson().toJson(doc.read("$"));
Assuming that parsed JSON can be represented in memory as a Map, you can build an API similar to JsonPath that looks like:
void update(Map<String, Object> json, String path, Object newValue);
I've quickly done a gist of a dirty implementation for simple specific paths (no support for conditions and wildcards) that can traverse json tree, E.g. $.store.name, $.store.books[0].isbn. Here it is: MutableJson.java. It definitely needs improvement, but can give a good start.
Usage example:
import java.util.*;
public class MutableJson {
public static void main(String[] args) {
MutableJson json = new MutableJson(
new HashMap<String, Object>() {{
put("store", new HashMap<String, Object>() {{
put("name", "Some Store");
put("books", Arrays.asList(
new HashMap<String, Object>() {{
put("isbn", "111");
}},
new HashMap<String, Object>() {{
put("isbn", "222");
}}
));
}});
}}
);
System.out.println("Before:\t" + json.map());
json.update("$.store.name", "Book Store");
json.update("$.store.books[0].isbn", "444");
json.update("$.store.books[1].isbn", "555");
System.out.println("After:\t" + json.map());
}
private final Map<String, Object> json;
public MutableJson(Map<String, Object> json) {
this.json = json;
}
public Map<String, Object> map() {
return json;
}
public void update(String path, Object newValue) {
updateJson(this.json, Path.parse(path), newValue);
}
private void updateJson(Map<String, Object> data, Iterator<Token> path, Object newValue) {
Token token = path.next();
for (Map.Entry<String, Object> entry : data.entrySet()) {
if (!token.accept(entry.getKey(), entry.getValue())) {
continue;
}
if (path.hasNext()) {
Object value = token.value(entry.getValue());
if (value instanceof Map) {
updateJson((Map<String, Object>) value, path, newValue);
}
} else {
token.update(entry, newValue);
}
}
}
}
class Path {
public static Iterator<Token> parse(String path) {
if (path.isEmpty()) {
return Collections.<Token>emptyList().iterator();
}
if (path.startsWith("$.")) {
path = path.substring(2);
}
List<Token> tokens = new ArrayList<>();
for (String part : path.split("\\.")) {
if (part.matches("\\w+\\[\\d+\\]")) {
String fieldName = part.substring(0, part.indexOf('['));
int index = Integer.parseInt(part.substring(part.indexOf('[')+1, part.indexOf(']')));
tokens.add(new ArrayToken(fieldName, index));
} else {
tokens.add(new FieldToken(part));
}
};
return tokens.iterator();
}
}
abstract class Token {
protected final String fieldName;
Token(String fieldName) {
this.fieldName = fieldName;
}
public abstract Object value(Object value);
public abstract boolean accept(String key, Object value);
public abstract void update(Map.Entry<String, Object> entry, Object newValue);
}
class FieldToken extends Token {
FieldToken(String fieldName) {
super(fieldName);
}
#Override
public Object value(Object value) {
return value;
}
#Override
public boolean accept(String key, Object value) {
return fieldName.equals(key);
}
#Override
public void update(Map.Entry<String, Object> entry, Object newValue) {
entry.setValue(newValue);
}
}
class ArrayToken extends Token {
private final int index;
ArrayToken(String fieldName, int index) {
super(fieldName);
this.index = index;
}
#Override
public Object value(Object value) {
return ((List) value).get(index);
}
#Override
public boolean accept(String key, Object value) {
return fieldName.equals(key) && value instanceof List && ((List) value).size() > index;
}
#Override
public void update(Map.Entry<String, Object> entry, Object newValue) {
List list = (List) entry.getValue();
list.set(index, newValue);
}
}
A JSON string can be easily parsed into a Map using Jackson:
Map<String,Object> userData = new ObjectMapper().readValue("{ \"store\": ... }", Map.class);
Just answering for folks landing on this page in future for reference.
You could consider using a Java implementation of jsonpatch. RFC can be found here
JSON Patch is a format for describing changes to a JSON document. It can be used to avoid sending a whole document when only a part has changed. When used in combination with the HTTP PATCH method it allows partial updates for HTTP APIs in a standards compliant way.
You can specify the operation that needs to be performed (replace, add....), json path at which it has to be performed, and the value which should be used.
Again, taking example from the RFC :
[
{ "op": "test", "path": "/a/b/c", "value": "foo" },
{ "op": "remove", "path": "/a/b/c" },
{ "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] },
{ "op": "replace", "path": "/a/b/c", "value": 42 },
{ "op": "move", "from": "/a/b/c", "path": "/a/b/d" },
{ "op": "copy", "from": "/a/b/d", "path": "/a/b/e" }
]
For Java implementation, I have not used it myself, but you can give a try to https://github.com/fge/json-patch
So in order to change a value within a JSon string, there are two steps:
Parse the JSon
Modify the appropriate field
You are trying to optimize step 2, but understand that you are not going to be able to avoid step 1. Looking at the Json-path source code (which, really, is just a wrapper around Jackson), note that it does do a full parse of the Json string before being able to spit out the read value. It does this parse every time you call read(), e.g. it is not cached.
I think this task is specific enough that you're going to have to write it yourself. Here is what I would do:
Create an object that represents the data in the parsed Json string.
Make sure this object has, as part of it's fields, the Json String pieces that you do not expect to change often.
Create a custom Deserializer in the Json framework of your choice that will populate the fields correctly.
Create a custom Serializer that uses the cached String pieces, plus the data that you expect to change
I think the exact scope of your problem is unusual enough that it is unlikely a library already exists for this. When a program receives a Json String, most of the time what it wants is the fully deserialized object - it is unusual that it needs to FORWARD this object on to somewhere else.