LS Price Increase Launch

The Price of all “Learn Spring” course packages will increase by $40 on next Friday:

>> GET ACCESS NOW

1. Overview

In this article, we'll explore different ways to extract all the nested keys from a JSON using JsonNode. We 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.

Tree model isn't limited to reading regular Java objects only. There is a one-to-one mapping between the JSON fields and tree model. Thereby, each object, whether or not POJO, can be represented as a node.
Hence, 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 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 get the following keys:

[Name, Age, BookInterests, FoodInterests]

In order to get all the inner nested nodes, we need to call 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 check whether a JSON value is an object or array. If yes, we traverse the value object as well to fetch inner nodes.
As a result, we 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 fields() method of 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 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 traverse() method of 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;
}

 3.3. Using Map

We can use the readValue() method of 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);
                    }
                });
            }
        });
}

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

4. Conclusion

We've seen 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.

LS Price Increase Launch

The Price of all “Learn Spring” course packages will increase by $40 on next Friday:

>> GET ACCESS NOW
Jackson footer banner
guest
0 Comments
Inline Feedbacks
View all comments