3

I have an Object that looks like this:

{
/test1: {
  get: {
   tags: [
     "restcalls"
   ]
 }
},
/test2: {
  put: {
   tags: [
     "restcalls"
   ]
 }
}
}

I am retrieving above object like this:

HashMap<?, ?> json = new ObjectMapper().readValue(str, HashMap.class);

But what would be the best way to retrieve tags and replace them with some other keyword lets say "my rest calls". Please note, get, put can be any other variable names too so its dynamic but tags will be always under get, put.

hagrawal7777
  • 14,103
  • 5
  • 40
  • 70
fscore
  • 2,567
  • 7
  • 40
  • 74
  • Don't use `HashMap`. Use `TreeNode` and traverse it appropriately. – Sotirios Delimanolis Dec 03 '15 at 22:46
  • can you provide a example and why TreeNode? – fscore Dec 06 '15 at 08:17
  • Since you’re already using an object mapper to convert the JSON, why not convert each test object into a real ‘TestObject` POJO that holds the `Method` object, which holds an array of tags? Traversing a complex object graph may not be maintainable in the future. I use Commons BeanUtils (http://commons.apache.org/proper/commons-beanutils) to convert objects from one type to another, I’m happy to provide an example if you wish. – johnnieb Dec 07 '15 at 21:19

2 Answers2

4

You have already chosen Jackson as your Java-JSON library (which I could say is a good choice) so problem at your hand is how to use Jackson to traverse and update the JSON in best way, but those who are even deciding on which Java-JSON library to use can read here which compares below mentioned 7 Java-JSON libraries. Below is conclusion excerpt from link:

As a conclusion, if you know that you are going to use only a small amount of data in your application and you wish to store or read it to and from JSON format, you should consider using Flexjson or Gson. If you are going to use large amounts of data and wish to store or read it to and from JSON format, you should consider using Jackson or JSON-lib.

  • Jackson
  • Google-GSON
  • JSON-lib
  • Flexjson
  • json-io
  • genson
  • JSONiJ


Jackson

Most important thing to understand is that Jackson offers three alternative methods for processing JSON (read more here), so appropriate approach should be chosen wisely considering the specific requirements.

  1. Streaming API (aka "Incremental parsing/generation"): It reads and writes JSON content as discrete events. Best case use: Iterating over Event (or, token) stream. Analogous to: SAX and Stax

  2. Tree Model: It provides a mutable in-memory tree representation of a JSON document. The tree model is similar to the XML DOM. Best case use: Binding Json data into Java Objects. Analogous to: JAXB

  3. Data Binding: It converts JSON to and from POJOs based either on property accessor conventions or annotations. There are two variants: simple and full data binding. Best case use: Building a tree structure (from Json) and traversing it using suitable methods. Analogous to: DOM

    • Simple data binding means converting to and from Java Maps, Lists, Strings, Numbers, Booleans and nulls
    • Full data binding means converting to and from any Java bean type (as well as "simple" types mentioned above)

Typically using "Tree Model" approach is considered as best from "performance perspective" because it gives advantages of traversing a tree.

Other options has also its own advantages like Streaming API has less memory and CPU overhead while Data binding is convenient to use because it provides List and Map representation of JSON data. However, with so much processing power and memory, these days what really matters is performance.

If your JSON data is huge and then analyze that in a tree representation whether tree would have wider width or longer length. If it has wider width then definitely using "Tree Model" will have performance advantages associated with a tree processing (make sure you are mapping in chunks and not whole stuff in one shot). But if tree is lengthier, meaning doesn't have good width but is like a very long tail, then consider using "Streaming API" because in that case advantages of tree processing cannot be substantiated. If data is small then it hardly matters and you can use "Tree Model" as default.

Currently you are using "Data Binding" approach but recommendation is to use "Tree Model" because all you need is traversal and manipulation of JSON. Looking at information you have given, it doesn't look like you need to convert your JSON into Java objects so "data binding" approach looks like a bad idea.

Jackson Tree Model

Jackson's com.fasterxml.jackson.databind.JsonNode forms the basis of Jackson's tree model. Think of Jackson's Tree model as a DOM of HTML document, the way DOM represents complete HTML document in a tree structure, Jackson's Tree model represent complete JSON string in a tree strcuture.

Below is the code which will help you use Jackson's Tree model and achieve what you are looking for:

import java.io.IOException;
import java.util.Iterator;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;


public class Test {

    public static void main(String[] args) throws JsonProcessingException, IOException {
        String jsonString = "{\"test1\": {\"get\": {\"tags\": [\"restcalls1\"]}}, \"test2\": {\"put\": {\"tags\": [\"restcalls2\"] }}}";
        ObjectMapper objectMapper = new ObjectMapper();
        JsonNode rootNode = objectMapper.readTree(jsonString);

        Iterator<JsonNode> iterator2 = rootNode.iterator();
        while (iterator2.hasNext()) {
            JsonNode node2 = iterator2.next().findParent("tags");
            ObjectNode objectNode = (ObjectNode) node2;
            objectNode.putArray("tags").add("my rest calls");
        }

        Iterator<JsonNode> iterator = rootNode.iterator();
        while (iterator.hasNext()) {
            JsonNode node2 = iterator.next();
            System.out.println(node2);
        }

    }
}

Tree model advantages:

Read here for more details
  • If the structure of Json content is highly irregular, it may be difficult (or impossible) to find or create equivalent Java object structure. Tree model may be the only practical choice.
  • For displaying any JSON content. Tree model is a natural choice for internal access and manipulation.
  • Since we do not need specific Java objects to bind to, there may less code to write.

Further readings:


Based on OP's comment:

Below code shows how you can traverse and print whole JSON. In this way, you can traverse whole JSON and then access elements you are looking for. Key think to note is that you need to know the element name, even if you use Stream API approach then also you should know the element name to look for, see Stream API example here

In any case, you can always choose to traverse the whole JSON (as shown in example below) or do specific and direct manipulation (as shown in example above)

package com.him.services;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;


public class Test {

    public static void main(String[] args) throws JsonProcessingException, IOException {
        jacksonTest();
    }

    private static void jacksonTest() throws JsonProcessingException, IOException {
        String jsonString = "{\"test1\": {\"get\": {\"tags\": [\"restcalls1\"]}}, \"test2\": {\"put\": {\"tags\": [\"restcalls2\"] }}}";
        ObjectMapper objectMapper = new ObjectMapper();
        JsonNode rootNode = objectMapper.readTree(jsonString);
        ArrayList<JsonNode> nodeList = new ArrayList<JsonNode>();
        nodeList.add(rootNode);

        printCompleteJson(nodeList);
    }

    private static void printCompleteJson(ArrayList<JsonNode> rootNode) throws IOException {
        for (int i = 0; i < rootNode.size(); i++) {
            Iterator<JsonNode> iterator = rootNode.get(i).iterator();
            JsonNode node = null;
            ArrayList<JsonNode> nodeList = new ArrayList<JsonNode>();
            boolean isEmpty = true;
            while (iterator.hasNext()) {
                isEmpty = false;
                node = iterator.next();
                nodeList.add(node);
                System.out.println(node);
            }
            if(isEmpty){
                return;
            }
            printCompleteJson(nodeList);
        }
    }
}
hagrawal7777
  • 14,103
  • 5
  • 40
  • 70
  • But that `get`, `put` can be any of the HTTP verbs so I dont want to explicitly specify that to retrieve value but automatically detect it. – fscore Dec 07 '15 at 20:28
  • I was trying to give you more on core concepts, so that small tweaks or requirement changes like this should not be an issue once you have grabbed the concepts. I have edited my answer to address you concern. Please let me know in case of any question. Basically you should understand that in how many different ways you can achieve same thing using Jackson or any other library. All you need is picking up an approach and reading its documentation to make yourself aware about available API in it, which you can then use to implement your requirement. – hagrawal7777 Dec 07 '15 at 21:05
  • How's it going, do you have any questions ?? – hagrawal7777 Dec 10 '15 at 17:15
  • I loved your explanation. I did achieve the above even before you posted the response but my pain point was that I dont know what `http verb` it is and retrieve JSON element properly and I still dont. – fscore Dec 11 '15 at 01:15
  • Thanks. Can you please elaborate your concern about "http verb", its not clear .. That's a JSON, now it have anything not even HTTP method names but may also some protocol names .. Using "Jackson" and similar kind of JSON serializer and de-serializer libraries, you can traverse the thing .. – hagrawal7777 Dec 11 '15 at 13:25
  • Right but it's my JSON and I am saying that tags will be contained in http verbs I.e. Get, put post,delete and inside each block, there is tags. – fscore Dec 11 '15 at 13:28
  • Please check my latest edit .. I guess what you needed was whole tree traversal .. Once you are able to traverse whole tree, you can look for specific elements .. I hv mentioned in answer as well that one important thing is that you should know what specific element/key to look for in the JSON .. Please let me know in case of any further question .. – hagrawal7777 Dec 11 '15 at 15:11
  • That's exactly what I am saying I don't know if it will be test1-> get-> tags or it will be test2-> post-> tags or test3-> put-> tags – fscore Dec 11 '15 at 16:43
  • So, you can traverse whole JSON and then take appropriate decision .. And if you know exactly which element to update then you can use first code example .. – hagrawal7777 Dec 11 '15 at 16:47
0

You can use Jackson's JsonNode classes to traverse the entire JSON and change the node value of tags. See the code below:

public class Tags {

    public static void main(String[] args) throws Exception {
        InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream("tags.json");
        ObjectMapper om = new ObjectMapper();
        JsonNode node = om.readTree(in);
        recursiveFind(node);
        System.out.println(node); //Prints {"test1":{"get":{"tags":["my rest calls"]}},"test2":{"put":{"tags":["my rest calls"]}}}
    }

    private static void recursiveFind(JsonNode node) {
        if (!node.isObject()) {
            return;
        }

        JsonNode tags = node.get("tags");
        if (tags != null) {
            ArrayNode arry = (ArrayNode) tags;
            arry.removeAll();
            arry.add("my rest calls");
        }

        Iterator<JsonNode> it = node.elements();
        while (it.hasNext()) {
            JsonNode jsonNode = it.next();
            recursiveFind(jsonNode);
        }
    }
}
Mohit
  • 1,740
  • 1
  • 15
  • 19