How to add new node to Json using JsonPath? - java

I'm working with JSON and facing some problems.
I want to insert/update a path in a JSON object. In the case that the path doesn't exist, it will be created then I insert a new value. In case that it exits, it will be updated by a new value
For example, I want to add new path like this:
val doc = JsonPath.parse(jsonString)
doc.add("$.user.name", "John")
but I always get this error, because the path doesn't exist:
class com.jayway.jsonpath.PathNotFoundException : Missing property in path $['user']
Therefore I want to create a new path if it does not exist.
This is my code, but jsonString doesn't change:
var jsonString = "{}" val conf = Configuration.defaultConfiguration().addOptions(Option.DEFAULT_PATH_LEAF_TO_NULL).addOptions(Option.SUPPRESS_EXCEPTIONS)
JsonPath.using(conf).parse(jsonString).set(JsonPath.compile("$.user.name"), "John")
Log.d("TAG", "new json = $jsonString")
Please give me your advice. Thank you very much!!

I tried three different JSON libraries with support of JsonPath/JsonPointer (Jackson, JsonPath and JSON-P) and none of them is able to reconstruct JSON object hierarchy in case of missing parent nodes. So I came up with my own solution for adding new values to JSON object using Jackson/JsonPointer as it allows to navigate through JsonPointer parts.
private static final ObjectMapper mapper = new ObjectMapper();
public void setJsonPointerValue(ObjectNode node, JsonPointer pointer, JsonNode value) {
JsonPointer parentPointer = pointer.head();
JsonNode parentNode = node.at(parentPointer);
String fieldName = pointer.last().toString().substring(1);
if (parentNode.isMissingNode() || parentNode.isNull()) {
parentNode = StringUtils.isNumeric(fieldName) ? mapper.createArrayNode() : mapper.createObjectNode();
setJsonPointerValue(parentPointer, parentNode); // recursively reconstruct hierarchy
}
if (parentNode.isArray()) {
ArrayNode arrayNode = (ArrayNode) parentNode;
int index = Integer.valueOf(fieldName);
// expand array in case index is greater than array size (like JavaScript does)
for (int i = arrayNode.size(); i <= index; i++) {
arrayNode.addNull();
}
arrayNode.set(index, value);
} else if (parentNode.isObject()) {
((ObjectNode) parentNode).set(fieldName, value);
} else {
throw new IllegalArgumentException("`" + fieldName + "` can't be set for parent node `"
+ parentPointer + "` because parent is not a container but " + parentNode.getNodeType().name());
}
}
Usage:
ObjectNode rootNode = mapper.createObjectNode();
setJsonPointerValue(rootNode, JsonPointer.compile("/root/array/0/name"), new TextNode("John"));
setJsonPointerValue(rootNode, JsonPointer.compile("/root/array/0/age"), new IntNode(17));
setJsonPointerValue(rootNode, JsonPointer.compile("/root/array/4"), new IntNode(12));
setJsonPointerValue(rootNode, JsonPointer.compile("/root/object/num"), new IntNode(81));
setJsonPointerValue(rootNode, JsonPointer.compile("/root/object/str"), new TextNode("text"));
setJsonPointerValue(rootNode, JsonPointer.compile("/descr"), new TextNode("description"));
System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(rootNode));
This generates and prints the following JSON object:
{
"root" : {
"array" : [ {
"name" : "John",
"age" : 17
}, null, null, null, 12 ],
"object" : {
"num" : 81,
"str" : "text"
}
},
"descr" : "description"
}
For sure, this doesn't cover all corner cases but works in most of the cases. Hope this helps someone else.

To create a new node try put(path, key, object) on the WriteContext interface implemented by the result of JsonPath.parse(jsonString).

You can do it as follows:
JsonPath.parse(jsonString).set(JsonPath.compile("$.user.name"), "John");

Related

How to update a certain value inside this jackson JsonNode?

TLDR:
I want to update certain value of a JsonNode key dependsOn and return the result as a JsonNode. Currently I'm converting the value to a String, slicing the characters and then using ObjectMapper to convert the string back to JsonNode
I have a json object like shown below
{
"name": "somename",
"type": "sometype",
"description": "some desc",
"properties": {
"path": "some path",
"dependsOn": [
"ABC:zzz","DEF:sdc","GHI:ere"
],
"checkpoint": "some checkpoint",
"format": "some format",
"output": "some output",
"table": "some table"
}
}
I'm currently parsing the above json data and fetching the dependsOn as JsonNode element (as shown below)
JsonNode components = model.get("properties");
JsonNode dependsOn = components.get("dependsOn");
When I print dependsOn it looks like this "["ABC:zzz","DEF:sdc","GHI:ere"]"
My requirement was to strip everything after : from the dependsOn array
This below code helped me to convert the JsonNode to String and then strip :whatever then convert it back to JsonNode
if (dependsOn != null && !dependsOn.isEmpty()) {
String dependsOnString =
components
.get("dependsOn")
.get(0)
.textValue()
.substring(
0,
(components.get("dependsOn").get(0).textValue().lastIndexOf(":") != -1)
? components.get("dependsOn").get(0).textValue().lastIndexOf(":")
: components.get("dependsOn").get(0).textValue().length());
ObjectMapper mapper = new ObjectMapper();
dependsOn = mapper.readTree("[\"" + dependsOnString + "\"]");
}
Input:
"["ABC:zzz","DEF:sdc","GHI:ere"]"
Output
"["ABC","DEF:sdc","GHI:ere"]"
Above code only strip the first element of the array I can loop and perform the same for rest of the elements though. But I have a couple of questions regarding whatever I'm trying to do
Firstly, am I doing this in a right way or is there a simpler
technique to do this? instead of converting it to string and then
again to JsonNode..
Next, I've only done this to the first element of the array
and I want to loop through and do this for all the elements of the array. Is there a simpler solution to this instead of using a for/while loop?
This should work, without convert to string and parse again to jsonNode
JsonNode prop = node.get("properties");
JsonNode arrayCopy = prop.get("dependsOn").deepCopy();
var array = ((ObjectNode)prop).putArray("dependsOn");
IntStream.range(0, arrayCopy.size())
.forEach(index -> {
String elem = arrayCopy.get(index).asText();
String finalElem = elem.substring(0,elem.contains(":") ? elem.lastIndexOf(':') : elem.length());
array.add(finalElem);
});
Since my usecase suggests my dependsOn value should not be overridden at node level, I had to convert the JsonNode to String and then used the regular expression matcher to replace :xyz with an empty string in each element then convert it back to JsonNode
String pattern = ":[a-zA-Z]+";
String newDependsOn = dependsOn.toString().replaceAll(pattern, "");
ObjectMapper mapper = new ObjectMapper();
dependsOn = mapper.readTree(newDependsOn);
#Gautham's solution did work too but what I think is it was overriding at the root and the old value wasn't available anymore outside the loop
You can iterate the dependsOn after casting it to ArrayNode and set value to it:
ArrayNode array = ((ArrayNode) dependsOn);
List<String> newValues = new ArrayList<>();
for(int i=0;i<array.size();i++) {
newValues.add(array.get(i).asText().split(":")[0]);
}
array.removeAll();
newValues.forEach(array::add);
EDIT: If you don't want your original dependsOn to be updated then use:
JsonNode copy = dependsOn.deepCopy();
// or you could invoke `deepCopy` on the `ArrayNode` as well
Now pass this copy object for slicing operation. So that the original json remains unchanged.

Mapping Json Array to POJO using Jackson

I have a JSON array of the form:
[
[
1232324343,
"A",
"B",
3333,
"E"
],
[
12345424343,
"N",
"M",
3133,
"R"
]
]
I want to map each element of the parent array to a POJO using the Jackson library. I tried this:
ABC abc = new ABC();
ObjectMapper mapper = new ObjectMapper();
JsonNode jsonNode = mapper.readTree(data).get("results");
if (jsonNode.isArray()) {
for (JsonNode node : jsonNode) {
String nodeContent = mapper.writeValueAsString(node);
abc = mapper.readValue(nodeContent,ABC.class);
System.out.println("Data: " + abc.getA());
}
}
where ABC is my POJO class and abc is the object but I get the following exception:
com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of com.demo.json.model.ABC
EDIT:
My POJO looks like this:
class ABC{
long time;
String a;
String b;
int status;
String c;
}
Can someone suggest a solution for this?
EDIT 2: After consulting a lot of answers on StackOverflow and other forums, I came across one solution. I mapped the returned value of readValue() method into an array of POJO objects.
ABC[] abc = mapper.readValue(nodeContent, ABC[].class);
But now I am getting a separate exception
Can not construct instance of ABC: no long/Long-argument constructor/factory method to deserialize from Number value (1552572583232)
I have tried the following but nothing worked:
1. Forcing Jackson to use ints for long values using
mapper.configure(DeserializationFeature.USE_LONG_FOR_INTS, true);
2. Using wrapper class Long instead of long in the POJO
Can anyone help me with this?
You can use ARRAY shape for this object. You can do that using JsonFormat annotation:
#JsonFormat(shape = Shape.ARRAY)
class ABC {
And deserialise it:
ABC[] abcs = mapper.readValue(json, ABC[].class);
EDIT after changes in question.
You example code could look like this:
JsonNode jsonNode = mapper.readTree(json);
if (jsonNode.isArray()) {
for (JsonNode node : jsonNode) {
String nodeContent = mapper.writeValueAsString(node);
ABC abc = mapper.readValue(nodeContent, ABC.class);
System.out.println("Data: " + abc.getA());
}
}
We can use convertValue method and skip serializing process:
JsonNode jsonNode = mapper.readTree(json);
if (jsonNode.isArray()) {
for (JsonNode node : jsonNode) {
ABC abc = mapper.convertValue(node, ABC.class);
System.out.println("Data: " + abc.getA());
}
}
Or even:
JsonNode jsonNode = mapper.readTree(json);
ABC[] abc = mapper.convertValue(jsonNode, ABC[].class);
System.out.println(Arrays.toString(abc));
Your json does not map to the pojo that you have defined. For the pojo that you have defined, the json should be of the form below.
{
"time:1232324343,
"a":"A",
"b":"B",
"status":3333,
"c":"E"
}

Remove element.field from ArrayNode

I have such json ArrayNode and I need to remove from each element for example field "xxx" using ObjectMapper, ArrayNode, JsonNode or ObjectNode. But without Gson and #JsonIgnore etc.
"arrayNode": [
{
"xxx": {},
"yyy": {}
},
{
"xxx": {},
"yyy": {}
}
]
I am not sure whether this problem has been solved or not. But following code snippet shows how to remove a field whose key is xxx from JSON node. And a JsonNode cannot perform insertion or deletion, so you have to cast it to ObjectNode for further manipulation.
Code snippet
ObjectMapper mapper = new ObjectMapper();
JsonNode rootNode = mapper.readTree(jsonStr);
rootNode.get("arrayNode").forEach(e -> {
if (e.has("xxx")) {
ObjectNode objNode = (ObjectNode) e;
objNode.remove("xxx");
}
});
System.out.println(rootNode.toString());
Console output
{"arrayNode":[{"yyy":{}},{"yyy":{}}]}
You can use this maven dependency : http://mvnrepository.com/artifact/org.json/json/20160212
It's very simple to understated and use. ex:
JSONObject obj = "YOUR_JSON_STRING";
JSONArray result = obj.getJSONArray("YOUR_STRING_KEY");
for(JSONObject elem : result){
String out = elem.getString("xxx");
}
More you can read at : https://developer.android.com/reference/org/json/JSONArray.html
Good luck

get elements from json

I want to have a java list for all elements which are in the "in" or "out" element.
My json string:
{"in":[
{"id":4,"ip":"192.168.0.20","pinSysNo":4,"pinSysName":"pg6","folderName":"gpio4_pg6","alias":"d","direction":"digital_in"},
{"id":3,"ip":"192.168.0.20","pinSysNo":3,"pinSysName":"pb18","folderName":"gpio3_pb18","alias":"c","direction":"digital_out"}
],
"out":[
{"id":1,"ip":"192.168.0.20","pinSysNo":1,"pinSysName":"pg3","folderName":"gpio1_pg3","alias":"a","direction":"digital_in"},
{"id":2,"ip":"192.168.0.20","pinSysNo":2,"pinSysName":"pb16","folderName":"gpio2_pb16","alias":"b","direction":"digital_in"}
]
}:""
Until now I did this way:
String message = json.findPath("in").textValue();
But this way can only access to the first hierarchy.
My json example show two elements in the "in" element. How I can get a list of these internal "in" elements?
You could use the library JSONSimple in order to parse your JSON data by this code:
JSONParser parser = new JSONParser();
JSONObject o = (JSONObject) parser.parse(yourJsonAsString);
JSONArray ins = (JSONArray) o.get("in");
JSONArray outs = (JSONArray) o.get("out");
String firstIpAddress = ((JSONObject) ins.get(0)).get("ip").toString();
Thank you for your help. I found an other way to find all sub elements.
Json example:
{"in":[
{"id":4,"ip":"192.168.0.20","pinSysNo":4,"pinSysName":"pg6","folderName":"gpio4_pg6","alias":"d","direction":"digital_in"},
{"id":3,"ip":"192.168.0.20","pinSysNo":3,"pinSysName":"pb18","folderName":"gpio3_pb18","alias":"c","direction":"digital_out"}
],
"out":[
{"id":1,"ip":"192.168.0.20","pinSysNo":1,"pinSysName":"pg3","folderName":"gpio1_pg3","alias":"a","direction":"digital_in"}
,{"id":2,"ip":"192.168.0.20","pinSysNo":2,"pinSysName":"pb16","folderName":"gpio2_pb16","alias":"b","direction":"digital_in"}
]
}
My solution:
JsonNode json = request().body().asJson();
Logger.info("JSON : " + json.findPath("in").findPath("id"));
Logger.info("JSON : " + json.findValues("in"));
List<JsonNode> ins = new org.json.simple.JSONArray();
ins = json.findValues("in");
for (final JsonNode objNode : ins) {
for (final JsonNode element : objNode) {
Logger.info(">>>>>" + element.findPath("id"));
//create my object for database
}
}
Now I can create my Object for the database.
#eztam thank you

Read part of a JSON String using Jackson

The JSON string is as follows
{
"rank":"-text_relevance",
"match-expr":"(label 'star wars')",
"hits":{
"found":7,
"start":0,
"hit":[
{"id":"tt1185834",
"data":{
"actor":["Abercrombie, Ian","Baker, Dee","Burton, Corey"],
"title":["Star Wars: The Clone Wars"]
}
},
.
.
.
{"id":"tt0121766",
"data":{
"actor":["Bai, Ling","Bryant, Gene","Castle-Hughes, Keisha"],
"title":["Star Wars: Episode III - Revenge of the Sith"]
}
}
]
},
"info":{
"rid":"b7c167f6c2da6d93531b9a7b314ad030b3a74803b4b7797edb905ba5a6a08",
"time-ms":2,
"cpu-time-ms":0
}
}
It has many fields, but I just have want the Data field. This won't work:
mapper.readvalue(jsonString,Data.class);
How do I make Jackson read just the "Data" field?
Jackson 2.3 now has a JsonPointer class you can use. There's a simple example in their quick overview for the release.
Usage is simple: for JSON like
{
"address" : { "street" : "2940 5th Ave", "zip" : 980021 },
"dimensions" : [ 10.0, 20.0, 15.0 ]
}
you could use expressions like:
JsonNode root = mapper.readTree(src);
int zip =root.at("/address/zip").asIntValue();
double height = root.add("/dimensions/1").asDoubleValue();// assuming it's the second number in there
I think that the easiest way to do this is using the Jackson TreeModel: let Jackson parse the JSON input into a JsonNode object that you then query, assuming some knowledge of the data structure. This way you can ignore most of the data, walking down the JsonNodes to the data that you want.
// String input = The JSON data from your question
ObjectMapper mapper = new ObjectMapper();
JsonNode rootNode = mapper.readValue(input.getBytes(), JsonNode.class);
// can also use ArrayNode here, but JsonNode allows us to get(index) line an array:
JsonNode hits = rootNode.get("hits");
// can also use ObjectNodes here:
JsonNode oneHit = null;
JsonNode dataObj = null;
int idx = 0;
Data data = null;
if (hits != null)
{
hits = hits.get("hit");
if (hits != null)
{
while ((oneHit = hits.get(idx)) != null)
{
dataObj = oneHit.get("data");
System.out.println("Data[" + idx + "]: " + dataObj);
idx++;
}
}
}
Output:
Data[0]: {"id":"tt1185834","data":{"actor":["Abercrombie, Ian","Baker, Dee","Burton, Corey"],"title":["Star Wars: The Clone Wars"]}}
Data[1]: {"id":"tt0121766","data":{"actor":["Bai, Ling","Bryant, Gene","Castle-Hughes, Keisha"],"title":["Star Wars: Episode III - Revenge of the Sith"]}}
You can still use your Data class implementation, but I believe this will require getting the String representing each data - as above relying on toString, or using JsonNode.getText() - and re-parsing it using the ObjectMapper:
mapper.readValue(dataArray, Data.class));
The alternative is to use the Jackson Streaming Model, and intercept the nodes yourself until you see the part of the input that marks the beginning of each data element, then consume the string and call objectMapper.readValue on the contents, for each string.
Json-path could be a very good alternative for such a requirement - if you are okay with a solution other than Jackson that is: http://code.google.com/p/json-path/

Categories