How to update a certain value inside this jackson JsonNode? - java

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.

Related

Simplest way to convert this array in to the specified one using Java

I have this String Json Payload
[
"key1":{
"atr1":"key1",
"atr2":"value1",
"atr3":"value2",
"atr4":"value3,
"atr5":"value4"
},
"key2":{
"atr1":"key2",
"atr2":"value5",
"atr3":"value6",
"atr4":value7,
"atr5":"value8"
}
]
and I want it to be converted in to the following format using Java
[
{
"atr2":"value1",
"atr3":"value2",
"atr4":"value3,
"atr5":"value4"
},
{
"atr2":"value5",
"atr3":"value6",
"atr4": "value7",
"atr5":"value8"
}
]
What would be the simplest way of transforming this ?
You cannot, because the example below is not valid json.
Check it out using this JSON validator.
If you paste this in (I've fixed some basic errors with lack of quotes)
{
{
"atr2":"value1",
"atr3":"value2",
"atr4":"value3",
"atr5":"value4"
},
{
"atr2":"value5",
"atr3":"value6",
"atr4":"value7",
"atr5":"value8"
}
}
You will get these errors ...
It can work if you change the target schema to something like this by using a json-array to contain your data.
[
{
"atr2":"value1",
"atr3":"value2",
"atr4":"value3",
"atr5":"value4"
},
{
"atr2":"value5",
"atr3":"value6",
"atr4":"value7",
"atr5":"value8"
}
]
If this works for you, then this problem can easily be solved by using the ObjectMapper class.
You use it to deserealize the original JSON into a class, which has two fields "key1" and "key2"
Extract the values of these fields and then just store them in an array ...
Serialize the array using the ObjectMapper.
Here a link, which explains how to use the ObjectMapper class to achieve the goals above.
EDIT:
So you'll need the following classes to solve the problem ...
Stores the object data
class MyClass {
String atr2;
String art3;
}
Then you have a container class, which is used to store the initial json.
class MyClassContainer {
MyClass key1;
MyClass key2;
}
Here's how you do the parse from the original json to MyClassContainer
var mapper = new ObjectMapper()
var json = //Get the json String somehow
var myClassContainer = mapper.readValue(json,MyClassContainer.class)
var mc1 = myClassContainer.getKey1();
var mc2 = myClassContainer.getKey2();
var myArray = {key1, key2}
var resultJson = mapper.writeValueAsString(myArray)
Assuming that you will correct the JSON into a valid one (which involves replacing the surrounding square braces with curly ones, and correct enclosure of attribute values within quotes), here's a simpler way which involves only a few lines of core logic.
try{
ObjectMapper mapper = new ObjectMapper();
mapper.configure( DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false );
HashMap<String, Data> map = mapper.readValue( jsonString, new TypeReference<HashMap<String, Data>>(){} );
String json = mapper.writeValueAsString( map.values() );
System.out.println( json );
}
catch( JsonProcessingException e ){
e.printStackTrace();
}
jsonString above is your original JSON corrected and valid JSON input.
Also notice the setting of FAIL_ON_UNKNOWN_PROPERTIES to false to allow atr1 to be ignored while deserializing into Data.
Since we are completely throwing away attr1 and its value, the Data class will represent all fields apart from that.
private static class Data{
private String atr2;
private String atr3;
private String atr4;
private String atr5;
}

How to get json value with jackson?

String url = "https://ko.wikipedia.org/w/api.php?action=query&format=json&list=search&srprop=sectiontitle&srlimit=1&srsearch=grand-theft-auto-v";
String test = restTemplate.getForObject(url, String.class);
Map<String, String> testToJson = objectMapper.readValue(test, Map.class);
testToJson is:
{
batchcomplete: "",
continue: {
sroffset: 1,
continue: "-||",
},
query: {
searchinfo: {
totalhits: 12
},
search: [
{
ns: 0,
title: "그랜드 테프트 오토 V",
pageid: 797633,
}
],
},
}
I want to get title value.
I try
testToJson.get("title")
but it returns null.
How to get title value with jackson?
You can deserialise it to a JsonNode and use JSON Pointer to get required field:
JsonNode node = mapper.readValue(jsonFile, JsonNode.class);
String title = node.at("/query/search/0/title").asText();
you could build a class for this json result then read from it.
public class Result {
private JsonNode searchinfo;
private JsonNode[] searches;
}
// then read:
Result testToJson = objectMapper.readValue(test, Result.class);
System.out.println(testToJson.getSearches(0).get("title"));
refer
It is impossible to read JSON into an instance of a generic class like that because the info about generics are used in compile time and already lost when program is running.
Jackson captures the data about generics using a sub-classed instance of TypeReference<T>.
Map<String, String> testToJson = objectMapper.readValue(test, new TypeReference<Map<String, String>>(){});
The problem with this approach is that Map<String, String> almost never describes complex data (like in the example) correctly. The example contains not only string values, there are numbers and even nested objects.
In situations like that, when you don't want or cannot write a class that describes the structure of the JSON, the better choice is parsing the JSON into a tree structure and traverse it. For example:
JsonNode node = objectMapper.readTree(test);
String title = node.get("query").get("search").get(0).get("title").asText();
Integer offset = node.get("continue").get("strOffset").asInt()

How to add new node to Json using JsonPath?

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");

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

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