How to replace json node in jsonNode or ObjectNode - java

I have a JSON node-like below. The structure of JsonNode will change dynamically.
Now I want to replace/update the value of a particular key.
Sample JSON One
{
"company": "xyz",
"employee": {
"name": "abc",
"address": {
"zipcode": "021566"
}
}
}
Sample JSON Two
{
"name": "abc",
"address": {
"zipcode": "021566"
}
}
I want to replace the zip code value 021566 to 566258. I know key name (zipcode), old and new zip code value but I don't know the path of zip code. I tried multiple ways using com.fasterxml.jackson.databind.JsonNode - put, replace
Is there any way to update in java?

JsonNodes are immutable, but you can find the value you want from a JsonNode, cast the original to an ObjectNode, replace the value, then cast that back to a JsonNode. It's a little odd, I know, but it worked for me.
public static void findAndReplaceJsonNode throws JsonProcessingException {
String jsonOne = "{ \"company\" : \"xyz\", \"address\" : { \"zipcode\" : \"021566\", \"state\" : \"FL\" } }";
String jsonTwo = "{ \"company\" : \"abc\", \"address\" : { \"zipcode\" : \"566258\", \"state\" : \"FL\" } }";
ObjectMapper mapper = new ObjectMapper();
JsonNode nodeOne = mapper.readTree(jsonOne);
JsonNode nodeTwo = mapper.readTree(jsonTwo);
JsonNode findNode = nodeTwo.get("address");
ObjectNode objNode = (ObjectNode) nodeOne;
objNode.replace("address", findNode);
nodeOne = (JsonNode) objNode;
System.out.println(nodeOne);
}
Output:
{"company":"xyz","address":{"zipcode":"566258","state":"FL"}}
Disclaimer: While I do some JSON parsing and processing on a regular basis, I certainly wouldn't say that I'm adept at it or tree traversals with them. There is more than likely a better way to find the value of a child in a JsonNode than by taking the entire child as a node, replacing it's one value and then replacing the node. This should work for you in a pinch, though :)

Related

Marshalling API response to List<POJO> with Jackson

I am having trouble parsing the response from an Adobe Campaign API endpoint into a POJO.
I am grabbing the data from the response:
String json = EntityUtils.toString(response.getEntity());
The data (heavily redacted) data looks like this:
{
"content": [
{
"PKey": "#9v59tLj9c.....",
"age": 36,
"birthDate": "1986-04-30",
"blackList": false,
...
},
{
"PKey": "#9f32tLj5c.....",
"age": 32,
"birthDate": "1999-05-11",
"blackList": false,
...
},
...
]
}
I'm instantiating a Jackson ObjectMapper and configuring it such that the root "content" node is ignored.
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
objectMapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
I have tried many different ways of parsing the data into my Profile POJO, without success. There's always an issue related to it being wrapped in "content" node, or being a list of one, or something. For brevity, the code below is for a single POJO, but I have also tried with List<Profile> since, as mentioned, the response is always a List of one or more.
// object mapper
Profile profile = objectMapper.readValue(json), Profile.class)
// ERROR: com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "content" (class com.example.adobecampaignprototype.Profile), not marked as ignorable (0 known properties: ])
// object reader
ObjectReader objectReader = objectMapper.readerFor(Profile.class).withRootName("content");
Profile profile = objectReader.readValue(json);
// ERROR: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type `com.example.adobecampaignprototype.Profile` from Array value (token `JsonToken.START_ARRAY`)
// array node
ArrayNode arrayNode = (ArrayNode) objectMapper.readTree(json).get("content");
// ERROR: com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "content" (class com.example.adobecampaignprototype.Profile), not marked as ignorable (0 known properties: ])
// json node
JsonNode jsonNodeRoot = objectMapper.readTree(json);
JsonNode jsonNodeNested = jsonNodeRoot.get("content");
JsonNode jsonNodeActual = jsonNodeNested.get(0) // to get profile at index 0
JsonNode jsonNodeActualValue = jsonNodeActual.get("PKey") // to read single property
I've tried the above in many combinations, but have never been able to successfully parse either a Profile or List. I have read the official docs exhaustively, been through tutorials on Baeldung and elsewhere. I feel like this should be a simple thing and there's probably something obvious that I'm overlooking, but unsure what it is. Would be grateful if someone could point me toward the EASY button.
Probably you are overthinking about it, for example if you take a semplified version of your json input file like below as a starting point:
{
"content": [
{
"PKey": "#9v59tLj9c....."
},
{
"PKey": "#9f32tLj5c....."
}
]
}
You can use part of the code you wrote, so one way to deserialize it is to convert your ArrayNode array of JsonNode into a Profile[] array:
#Data
public class Profile {
#JsonProperty("PKey")
private String pKey;
}
ArrayNode arrayNode = (ArrayNode) mapper.readTree(json).get("content");
Profile[] profiles = mapper.convertValue(arrayNode, Profile[].class);
//ok it prints the array [{"PKey":"#9v59tLj9c....."},{"PKey":"#9f32tLj5c....."}]
System.out.println(mapper.writeValueAsString(profiles));

Set child node as root node using jackson

The question is quite simple:
From this:
{
"categoryId":"some_id",
"properties": {
"id": "braja_de_nana",
"displayName": "test",
"longDescription": "<p>TESTE</p>",
"active": true,
"attributes": [
{
"name": "made",
"value": "THIS_BECOMES_A_NODE_VALUE",
"property": "THIS_BECOMES_A_NODE_NAME"
},
{
"name": "made",
"value": "THIS_BECOMES_A_NODE_VALUE_2",
"property": "THIS_BECOMES_A_NODE_NAME_2"
}
]
}
}
UPDATE
This should be the result:
It means that every array element of 'attributes' should become a new root node.
set 'property' from 'attributes' as the object node name.
set 'value' from 'attributes' as the object node value.
{
"categoryId":"some_id",
"THIS_BECOMES_A_NODE_VALUE":"THIS_BECOMES_A_NODE_NAME",
"THIS_BECOMES_A_NODE_NAME_2":"THIS_BECOMES_A_NODE_VALUE_2"
"properties": {
"id": "braja_de_nana",
"displayName": "test",
"longDescription": "<p>TESTE</p>",
"active": true
}
}
This is a challenge for me.
I can set new nodes into the root node.
Already got a map from 'attributes' and then tried to iterate them with forEach in order to put the result into one single node, but instead as shown I have to take the 'property' set it to the object name´s key, then get the value and set to its value.
UPDATE 2
#Override
public String toOccProductDTO(ProcessProductDTO processProductDTO) throws JsonProcessingException {
OccProductDTO occProductDTO = OccProductDTO.builder()
.categoryId(processProductDTO.getCategoryId())
.productType(processProductDTO.getCategoryId())
.properties(toOccProductPropertiesDTO(processProductDTO))
.build();
toOccProductPropertiesDTO(processProductDTO);
String tree = mapper.writeValueAsString(occProductDTO);
JsonNode root = mapper.readTree(tree);
JsonNode attributesNodeArray = ((ObjectNode) root.get("properties"))
.remove("p_specs");
Iterator<JsonNode> arrayNodes = attributesNodeArray.iterator();
while (arrayNodes.hasNext()) {
JsonNode node = arrayNodes.next();
root = ((ObjectNode)root).set(node.get("value").asText(), node.get("property"));
}
System.out.println(root.toPrettyString());
return null;
}
I got an: arrayNodes: Collection$EmptyIterator at that line.
Am I doing something wrong?
If you are trying to the attributes to the root node, you can remove that node and add its fields to the root.
The "attributes" node is an array with length 1, so you have to get the first element of the array to get the attribute fields.
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(json);
JsonNode attributesNodeArray = ((ObjectNode) root.get("properties"))
.remove("attributes");
JsonNode attributesNode = attributesNodeArray.get(0);
Iterator<String> fieldNames = attributesNode.fieldNames();
while (fieldNames.hasNext()) {
String name = fieldNames.next();
root = ((ObjectNode)root).set(name, attributesNode.get(name));
}
System.out.println(root.toPrettyString());
Output:
{
"categoryId" : "some_id",
"properties" : {
"id" : "braja_de_nana",
"displayName" : "test",
"longDescription" : "<p>TESTE</p>",
"active" : true
},
"name" : "made",
"value" : "some value",
"property" : "some_value"
}
UPDATE
For the updated question, you can do the following:
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(json);
JsonNode attributesNodeArray = ((ObjectNode) root.get("properties"))
.remove("attributes");
Iterator<JsonNode> arrayNodes = attributesNodeArray.iterator();
while (arrayNodes.hasNext()) {
JsonNode node = arrayNodes.next();
root = ((ObjectNode)root).set(node.get("value").asText(), node.get("property"));
}
System.out.println(root.toPrettyString());
Output:
{
"categoryId" : "some_id",
"properties" : {
"id" : "braja_de_nana",
"displayName" : "test",
"longDescription" : "<p>TESTE</p>",
"active" : true
},
"THIS_BECOMES_A_NODE_VALUE" : "THIS_BECOMES_A_NODE_NAME",
"THIS_BECOMES_A_NODE_VALUE_2" : "THIS_BECOMES_A_NODE_NAME_2"
}
Maybe it was more complex than expected.
It turns out that I solved the problem with Oboe´s help. Although he missed some points through his implementation plus some changes I could achieve the goal.
//Converts the parsed objects into Json String
String tree = mapper.writeValueAsString(occProductDTO);
//Reads the json string to JsonNode in order to manipulate it
JsonNode root = mapper.readTree(tree);
//Sets the chosen node where the new nodes should be created
JsonNode properties = root.path("properties");
//maps the two attribs needed
Map<String, String> attribs = processProductDTO.getProductDTO().getAttributes().stream()
.collect(Collectors.toMap(AttributeDTO::getProperty, AttributeDTO::getValue));
//Converts each attrib into a String list
List<String> props = attribs.entrySet().stream()
.sorted(Comparator.comparing(Map.Entry<String, String>::getValue).reversed())
.map(Map.Entry::getKey)
.collect(Collectors.toList());
List<String> names = attribs.entrySet()
.stream() .sorted(Comparator.comparing(Map.Entry<String,String>::getValue).reversed())
.map(Map.Entry::getValue)
.collect(Collectors.toList());
//iterates over the two lists adding the attribs to their corresponding position
Iterator<String> arrayNodes = props.listIterator();
Iterator<String> arrayNodes2 = names.listIterator();
while (arrayNodes.hasNext()) {
String node = arrayNodes.next();
String node2 = arrayNodes2.next();
properties = ((ObjectNode)properties).put(node, node2);
}
return mapper.writeValueAsString(root);
}
In the end, instead of passing a java object via #Post, I´m passing a json String by using "consumes = application/json, produces = "application.json"
That´s it!
Maybe it could be achieved and better implemented with java 8 stream, but for now it works.
Suggestion to improve the code are welcome!

Stringify all values in a newline delimited JSON by keys

I have a newline delimited JSON and I wish to change values of type map to strings using Java. An example is:
{"id": 1, "data": {}}
{"id": 1, "data": {"time": 43}}
{"id": 1, "data": {"class" : {"students" : [{"name" : "Jane"}]}}}
should be changed to
{"id": 1, "data": "{}"}
{"id": 1, "data": "{\"time\": 43}"}
{"id": 1, "data": "{\"class\" : {\"students\" : [{\"name\" : \"Jane\"}]}}"}
I wish to stringify all the data map values. How can I do this?
This is a problem that calls for a JSON processor, since this will provide for the most bullet-proof, simple solutions. Java doesn't come with built-in JSON processing, so this will require a third party library. One commonly used Java JSON library is Jackson.
Since this particular request involves working with arbitrary data formats, the Jackson Tree Model in the Jackson Databind library is a good fit.
String delimitedJson = "{\"id\": 1, \"data\": {}}\n"
+ "{\"id\": 1, \"data\": {\"time\": 43}}\n"
+ "{\"id\": 1, \"data\": {\"class\" : {\"students\" : [{\"name\" : \"Jane\"}]}}}";
StringBuilder output = new StringBuilder();
JsonMapper jsonMapper = JsonMapper.builder().build();
MappingIterator<ObjectNode> i =
jsonMapper.readerFor(ObjectNode.class).readValues(delimitedJson);
i.forEachRemaining((ObjectNode jsonNode) ->
output.append(convertObjectsToString(jsonMapper, jsonNode).toString())
.append("\n"));
System.out.println(output);
// ...
private ObjectNode convertObjectsToString(JsonMapper jsonMapper,
ObjectNode jsonNode) {
ObjectNode copy = jsonMapper.createObjectNode();
jsonNode.fields().forEachRemaining(e -> {
if (e.getValue() instanceof ObjectNode) {
copy.set(e.getKey(), new TextNode(e.getValue().toString()));
} else {
copy.set(e.getKey(), e.getValue());
}
});
return copy;
}
Explanation
The ObjectMapper class is the primary class in the Jackson Databind library, and JsonMapper is the JSON-format specific implementation.
After creating the instance (using JsonMapper.builder().build()), we iterate over each element of the newline-delimited list using ObjectReader.readValues(delimitedJson), which reads a number of whitespace separated JSON values from an input source. In this example, I'm assuming the root level JSON values in your list are objects, and not e.g. strings or ints.
MappingIterator<ObjectNode> i =
jsonMapper.readerFor(ObjectNode.class).readValues(delimitedJson);
i.forEachRemaining((ObjectNode jsonNode) ->
output.append(convertObjectsToString(jsonMapper, jsonNode).toString())
.append("\n"));
The custom convertObjectsToString method returns an ObjectNode, which is converted to JSON using toString().
The convertObjectsToString method starts by creating a new empty ObjectNode. For each property in the source node, if it's not a JSON object type, it is copied over directly. If it is a JSON object, it is converted to the JSON string representation of the object.
private ObjectNode convertObjectsToString(JsonMapper jsonMapper,
ObjectNode jsonNode) {
ObjectNode copy = jsonMapper.createObjectNode();
jsonNode.fields().forEachRemaining(e -> {
if (e.getValue() instanceof ObjectNode) {
copy.set(e.getKey(), new TextNode(e.getValue().toString()));
} else {
copy.set(e.getKey(), e.getValue());
}
});
return copy;
}

Adding changes to the JSON structure

I have the following JSON, generated in the Android application:
{
"Details": {
"ClaimDate": "08/10/2019",
"HFCode": "55555"
},
"Items": [
{
"Item": {
"ItemCode": "Y203",
"ItemPrice": "20",
"ItemQuantity": "1"
}
}
],
"Services": [
{
"Service": {
"ServiceCode": "X105",
"ServicePrice": "200",
"ServiceQuantity": "1"
}
}
]
}
On the server side, I need this structure
{
"details": {
"ClaimDate": "08/10/2019",
"HFCode": "55555"
},
"items": [
{
"itemCode": "Y200",
"itemPrice": 0,
"itemQuantity": 0
}
],
"services": [
{
"serviceCode": "X100",
"servicePrice": 0,
"serviceQuantity": 0
}
]
}
Is there a way to customize this on the Android application side?
I try to do it manually, but I can't get a satisfactory result
You can use a transformer function which will take the first json/object as input and returns the second json/object as output. Unfortunately, since your keys and data types are different, standard libraries will not able to do this. If you want to use Jackson or Gson, you will have to play with Custom (De) Serializers.
If you are using Jackson (One of the most popular JSON libraries) and you just want to transform the given JSON string into another one, then you can achieve this by following way:
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(jsonStr);
ObjectNode rootNew = mapper.createObjectNode();
rootNew.put("details", root.get("Details"));
JsonNode itemNode = root.get("Items").get(0).get("Item");
ObjectNode itemsNodeNew = mapper.createObjectNode();
itemsNodeNew.put("itemCode", itemNode.get("ItemCode"));
itemsNodeNew.put("itemPrice", itemNode.get("ItemPrice"));
itemsNodeNew.put("itemQuantity", itemNode.get("ItemQuantity"));
rootNew.put("items", mapper.createArrayNode().add(itemsNodeNew));
JsonNode serviceNode = root.get("Services").get(0).get("Service");
ObjectNode serviceNodeNew = mapper.createObjectNode();
serviceNodeNew.put("serviceCode", serviceNode.get("ServiceCode"));
serviceNodeNew.put("servicePrice", serviceNode.get("ServicePrice"));
serviceNodeNew.put("serviceQuantity", serviceNode.get("ServiceQuantity"));
rootNew.put("services", mapper.createArrayNode().add(serviceNodeNew));
System.out.println(rootNew.toString());
But if you want to convert the JSON string to POJO for further manipulation, you can directly deserialize and serialize it.

How to get count all json nodes using Jackson framework

Here is my user.json
{
"id":1,
"name":{
"first":"Yong",
"last":"Mook Kim"
},
"contact":[
{
"type":"phone/home",
"ref":"111-111-1234"
},
{
"type":"phone/work",
"ref":"222-222-2222"
}
]
},
{
"id":2,
"name":{
"first":"minu",
"last":"Zi Lap"
},
"contact":[
{
"type":"phone/home",
"ref":"333-333-1234"
},
{
"type":"phone/work",
"ref":"444-444-4444"
}
]
}
I would like count how many json object is in there. For example the above json has 2 json object id = 1 and id =2.
//tree model approach
ObjectMapper mapper = new ObjectMapper();
JsonNode rootNode = mapper.readTree(new File("user.json"));
List<JsonNode> listOfNodes = rootNode.findParents("first");
System.out.println(listOfNodes.size());
Giving me size = 1.
Can you please tell me what i am doing wrong?
Thanks
Your java code is correct but your json file is invalid.
Jackson parses only first valid element ("Yong").
To fix this just add [ at the begining and ] at the end of file (make it array).

Categories