Course – LS (cat=JSON/Jackson)

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Overview

In this tutorial, we’ll explore different ways to extract all the nested keys from a JSON using JsonNode. We’ll aim to traverse through a JSON string and collect key names in a list.

2. Introduction

The Jackson library uses a tree model to represent JSON data. The tree model provides us with an efficient way to interact with hierarchical data.

JSON objects are represented as nodes in the tree model. This makes it easier to perform CRUD operations on JSON content.

2.1. ObjectMapper

We use ObjectMapper class methods to read the JSON content. The ObjectMapper.readTree() method deserializes JSON and builds a tree of JsonNode instances. It takes a JSON source as input, and returns the root node of the tree model created. Subsequently, we can use the root node to traverse the entire JSON tree.

The tree model isn’t limited to reading regular Java objects only. There is a one-to-one mapping between the JSON fields and the tree model. Thereby, each object, whether POJO or not, can be represented as a node. Thus, we enjoy a flexible approach to represent JSON content as generic nodes.

To learn more, please refer to our article on Jackson ObjectMapper.

2.2. JsonNode

The JsonNode class represents a node in the JSON tree model. It can express JSON data in the following data types: Array, Binary, Boolean, Missing, Null, Number, Object, POJO, String. These data types are defined in the JsonNodeType enum.

3. Getting Keys From JSON

We’re using the following JSON as input in this article:

{
   "Name":"Craig",
   "Age":10,
   "BookInterests":[
      {
         "Book":"The Kite Runner",
         "Author":"Khaled Hosseini"
      },
      {
         "Book":"Harry Potter",
         "Author":"J. K. Rowling"
      }
   ],
   "FoodInterests":{
      "Breakfast":[
         {
            "Bread":"Whole wheat",
            "Beverage":"Fruit juice"
         },
         {
            "Sandwich":"Vegetable Sandwich",
            "Beverage":"Coffee"
         }
      ]
   }
}

Here we’re using a String object as input, but we can read JSON content from different sources, such as Filebyte[], URLInputStreamJsonParser, etc.

Now let’s discuss different approaches to fetch keys from a JSON.

3.1. Using fieldNames

We can use the fieldNames() method on a JsonNode instance to fetch the nested field names. It returns names of direct nested fields only.

Let’s try it with a simple example:

public List<String> getKeysInJsonUsingJsonNodeFieldNames(String json, ObjectMapper mapper) throws JsonMappingException, JsonProcessingException {

    List<String> keys = new ArrayList<>();
    JsonNode jsonNode = mapper.readTree(json);
    Iterator<String> iterator = jsonNode.fieldNames();
    iterator.forEachRemaining(e -> keys.add(e));
    return keys;
}

We’ll get the following keys:

[Name, Age, BookInterests, FoodInterests]

In order to get all the inner nested nodes, we need to call the fieldNames() method on nodes at each level:

public List<String> getAllKeysInJsonUsingJsonNodeFieldNames(String json, ObjectMapper mapper) throws JsonMappingException, JsonProcessingException {

    List<String> keys = new ArrayList<>();
    JsonNode jsonNode = mapper.readTree(json);
    getAllKeysUsingJsonNodeFieldNames(jsonNode, keys);
    return keys;
}
private void getAllKeysUsingJsonNodeFields(JsonNode jsonNode, List<String> keys) {

    if (jsonNode.isObject()) {
        Iterator<Entry<String, JsonNode>> fields = jsonNode.fields();
        fields.forEachRemaining(field -> {
            keys.add(field.getKey());
            getAllKeysUsingJsonNodeFieldNames((JsonNode) field.getValue(), keys);
        });
    } else if (jsonNode.isArray()) {
        ArrayNode arrayField = (ArrayNode) jsonNode;
        arrayField.forEach(node -> {
            getAllKeysUsingJsonNodeFieldNames(node, keys);
        });
    }
}

First, we’ll check whether a JSON value is an object or array. If yes, we’ll traverse the value object as well to fetch inner nodes.

As a result, we’ll get all the key names present in JSON:

[Name, Age, BookInterests, Book, Author,
  Book, Author, FoodInterests, Breakfast, Bread, Beverage, Sandwich, Beverage]

In the above example, we can also use the fields() method of the JsonNode class to get field objects instead of just field names:

public List<String> getAllKeysInJsonUsingJsonNodeFields(String json, ObjectMapper mapper) throws JsonMappingException, JsonProcessingException {

    List<String> keys = new ArrayList<>();
    JsonNode jsonNode = mapper.readTree(json);
    getAllKeysUsingJsonNodeFields(jsonNode, keys);
    return keys;
}

private void getAllKeysUsingJsonNodeFields(JsonNode jsonNode, List<String> keys) {

    if (jsonNode.isObject()) {
        Iterator<Entry<String, JsonNode>> fields = jsonNode.fields();
        fields.forEachRemaining(field -> {
            keys.add(field.getKey());
            getAllKeysUsingJsonNodeFieldNames((JsonNode) field.getValue(), keys);
        });
    } else if (jsonNode.isArray()) {
        ArrayNode arrayField = (ArrayNode) jsonNode;
        arrayField.forEach(node -> {
            getAllKeysUsingJsonNodeFieldNames(node, keys);
        });
    }
}

3.2. Using JsonParser

We can also use the JsonParser class for low-level JSON parsingJsonParser creates a sequence of iterable tokens from the given JSON content. Token types are specified as enums in the JsonToken class, as listed below:

  • NOT_AVAILABLE
  • START_OBJECT
  • END_OBJECT
  • START_ARRAY
  • FIELD_NAME
  • VALUE_EMBEDDED_OBJECT
  • VALUE_STRING
  • VALUE_NUMBER_INT
  • VALUE_NUMBER_FLOAT
  • VALUE_TRUE
  • VALUE_FALSE
  • VALUE_NULL

While iterating using JsonParser, we can check the token type and perform required operations. Let’s fetch all the field names for our example JSON string:

public List<String> getKeysInJsonUsingJsonParser(String json, ObjectMapper mapper) throws IOException {

    List<String> keys = new ArrayList<>();
    JsonNode jsonNode = mapper.readTree(json);
    JsonParser jsonParser = jsonNode.traverse();
    while (!jsonParser.isClosed()) {
        if (jsonParser.nextToken() == JsonToken.FIELD_NAME) {
            keys.add((jsonParser.getCurrentName()));
        }
    }
    return keys;
}

Here, we’ve used the traverse() method of the JsonNode class to get the JsonParser object. Similarly, we can create a JsonParser object using JsonFactory as well:

public List<String> getKeysInJsonUsingJsonParser(String json) throws JsonParseException, IOException {

    List<String> keys = new ArrayList<>();
    JsonFactory factory = new JsonFactory();
    JsonParser jsonParser = factory.createParser(json);
    while (!jsonParser.isClosed()) {
        if (jsonParser.nextToken() == JsonToken.FIELD_NAME) {
            keys.add((jsonParser.getCurrentName()));
        }
    }
    return keys;
}

As a result, we’ll get all the key names extracted from the example JSON content:

[Name, Age, BookInterests, Book, Author,
  Book, Author, FoodInterests, Breakfast, Bread, Beverage, Sandwich, Beverage]

Notice how concise the code is if we compare it to the other approaches we presented in this article.

 3.3. Using Map

We can use the readValue() method of the ObjectMapper class to deserialize JSON content to a Map. Consequently, we can extract JSON elements while iterating over the Map object. Let’s try to fetch all keys from our example JSON using this approach:

public List<String> getKeysInJsonUsingMaps(String json, ObjectMapper mapper) throws JsonMappingException, JsonProcessingException {
    List<String> keys = new ArrayList<>();
    Map<String, Object> jsonElements = mapper.readValue(json, new TypeReference<Map<String, Object>>() {
    });
    getAllKeys(jsonElements, keys);
    return keys;
}

private void getAllKeys(Map<String, Object> jsonElements, List<String> keys) {

    jsonElements.entrySet()
        .forEach(entry -> {
            keys.add(entry.getKey());
            if (entry.getValue() instanceof Map) {
                Map<String, Object> map = (Map<String, Object>) entry.getValue();
                getAllKeys(map, keys);
            } else if (entry.getValue() instanceof List) {
                List<?> list = (List<?>) entry.getValue();
                list.forEach(listEntry -> {
                    if (listEntry instanceof Map) {
                        Map<String, Object> map = (Map<String, Object>) listEntry;
                        getAllKeys(map, keys);
                    }
                });
            }
        });
}

Also in this case, after getting top-level nodes, we’ll traverse the JSON objects that have values as either objects (maps) or arrays to get nested nodes.

4. Conclusion

In this article, we learned different ways to read key names from JSON content. Henceforth, we can extend the traversal logic discussed in the article to perform other operations on JSON elements as required.

As always, the full source code of the examples can be found over on GitHub.

Course – LS (cat=JSON/Jackson)

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE
res – Jackson (eBook) (cat=Jackson)
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.