Jackson JsonNode to Java Collections
Java applications frequently interact with data in JSON (JavaScript Object Notation) format. Jackson Java Library provides tools for working with JSON data. One key aspect is converting raw JSON data represented by the JsonNode
class in Jackson to structured Java collections like Lists and Maps. This article explores various methods for transforming JsonNode
objects into typed Java collections.
1. Example JSON Data
Prerequisites: Ensure you have the Jackson Library added to your project. If you’re using Maven, include the following dependencies:
<dependencies> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.17.1</version> </dependency> </dependencies>
Let’s use the following JSON data for the examples in this article:
{ "name": "Duke", "age": 30, "address": { "street": "123 Main St", "city": "Anytown" }, "phoneNumbers": [ { "type": "home", "number": "555-555-5555" }, { "type": "work", "number": "555-555-5556" } ] }
This JSON object represents a person’s profile with several details. Additionally, the phoneNumbers
field is an array of objects, each representing different types of phone numbers.
2. Converting JsonNode
to List
and Map
2.1 Using ObjectMapper
with TypeReference
This method involves using the ObjectMapper
class with TypeReference
to convert a JsonNode
to a typed List
or Map
.
public class JsonNodeConversion { public static void main(String[] args) throws FileNotFoundException { // Create ObjectMapper instance ObjectMapper mapper = new ObjectMapper(); try { // Parse JSON file to JsonNode JsonNode jsonNode = mapper.readTree(new FileReader("src/main/resources/data.json")); // Convert JsonNode to Map Map<String, Object> map = mapper.convertValue(jsonNode, new TypeReference<Map<String, Object>>() { }); System.out.println("Map: " + map); // Convert JsonNode to List JsonNode phoneNumbersNode = jsonNode.get("phoneNumbers"); List<Map<String, Object>> list = mapper.convertValue(phoneNumbersNode, new TypeReference<List<Map<String, Object>>>() { }); System.out.println("List: " + list); } catch (IOException e) { System.out.println(" "+ e); } } }
In this method, we use the ObjectMapper
class along with TypeReference
to convert a JsonNode
to a typed List
or Map
. The TypeReference
class allows us to specify the target type in a generic way.
Here we read a JSON string into a JsonNode
and then convert it into a Map<String, Object>
and a List<Map<String, Object>>
. This method is straightforward and leverages Jackson’s type handling to automatically convert the JsonNode
to the desired types.
2.2 Using ObjectMapper
with readValue
Another method is to use the readValue
method of ObjectMapper
to convert a JsonNode
to a typed List
or Map
.
// Convert JsonNode to Map Map<String, Object> map = mapper.readValue(jsonNode.toString(), new TypeReference<Map<String, Object>>() { }); System.out.println("Map: " + map); // Convert JsonNode to List JsonNode phoneNumbersNode = jsonNode.get("phoneNumbers"); List<Map<String, Object>> list = mapper.readValue(phoneNumbersNode.traverse(), new TypeReference<List<Map<String, Object>>>() { }); System.out.println("List: " + list);
This method involves using the readValue
method of ObjectMapper
to directly read the JSON string and convert it into a typed collection.
2.3 Manually Traversing JsonNode
We can manually traverse the JsonNode
to convert it into a typed List
or Map
.
public class ManualJsonNodeConversion { public static void main(String[] args) { ObjectMapper mapper = new ObjectMapper(); try { JsonNode jsonNode = mapper.readTree(new FileReader("src/main/resources/data.json")); // Convert JsonNode to Map Map<String, Object> map = new HashMap<>(); Iterator<Map.Entry<String, JsonNode>> fields = jsonNode.fields(); while (fields.hasNext()) { Map.Entry<String, JsonNode> field = fields.next(); map.put(field.getKey(), field.getValue()); } System.out.println("Map: " + map); // Convert JsonNode to List JsonNode phoneNumbersNode = jsonNode.get("phoneNumbers"); List<Map<String, Object>> list = new ArrayList<>(); if (phoneNumbersNode.isArray()) { for (JsonNode node : phoneNumbersNode) { Map<String, Object> phoneNumberMap = new HashMap<>(); node.fields().forEachRemaining(entry -> phoneNumberMap.put(entry.getKey(), entry.getValue())); list.add(phoneNumberMap); } } System.out.println("List: " + list); } catch (IOException e) { System.out.println(" " + e); } } }
In this method, we manually traverse the JsonNode
to convert it into a typed List
or Map
. This approach involves iterating over the fields of the JsonNode
and populating a Map
or List
manually. While this method is more verbose, it provides complete control over the conversion process.
3. Using Custom Deserialization
We can create custom deserializers for more complex scenarios. Create a Person
class and ensure it has getters, setters, and a default constructor.
Define the Person Class
public class Person { public String name; public int age; @JsonDeserialize(using = AddressDeserializer.class) public Map<String, Object> address; @JsonDeserialize(using = PhoneNumbersDeserializer.class) public List<Map<String, Object>> phoneNumbers; public Person() { } public Person(String name, int age, Map<String, Object> address, List<Map<String, Object>> phoneNumbers) { this.name = name; this.age = age; this.address = address; this.phoneNumbers = phoneNumbers; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Map<String, Object> getAddress() { return address; } public void setAddress(Map<String, Object> address) { this.address = address; } public List<Map<String, Object>> getPhoneNumbers() { return phoneNumbers; } public void setPhoneNumbers(List<Map<String, Object>> phoneNumbers) { this.phoneNumbers = phoneNumbers; } }
The Person
class is a Java bean that represents a person with four fields: name
, age
, address
, and phoneNumbers
.
- The
address
field is aMap<String, Object>
, which can store a variety of key-value pairs representing address details. - The
phoneNumbers
field is aList<Map<String, Object>>
, allowing for a list of maps, each representing a phone number’s details. - The class includes a no-argument constructor, a parameterized constructor, and standard getters and setters for all fields.
3.1 Create the Custom Deserializers
We need to create a custom deserializer that extends JsonDeserializer
and implement the deserialize
method to parse the JsonNode
.
Create AddressDeserializer.java
: Create a custom deserializer for the address
field.
public class AddressDeserializer extends JsonDeserializer<Map<String, Object>> { @Override public Map<String, Object> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { Map<String, Object> addressMap = new HashMap<>(); ObjectMapper mapper = (ObjectMapper) p.getCodec(); JsonNode node = mapper.readTree(p); node.fields().forEachRemaining(field -> addressMap.put(field.getKey(), field.getValue().asText())); return addressMap; } }
The AddressDeserializer
class extends JsonDeserializer<Map<String, Object>>
and overrides the deserialize
method to provide custom deserialization logic for the address
field. This method uses an ObjectMapper
to read the JSON node and iterates over the fields of the address
node using the forEachRemaining
method. It populates a HashMap
with the address details, converting each field’s value to a string.
Create a custom deserializer for the phoneNumbers
field.
public class PhoneNumbersDeserializer extends JsonDeserializer<List<Map<String, Object>>> { @Override public List<Map<String, Object>> deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { List<Map<String, Object>> phoneNumbersList = new ArrayList<>(); ObjectMapper mapper = (ObjectMapper) p.getCodec(); JsonNode node = mapper.readTree(p); node.forEach(phoneNumberNode -> { Map<String, Object> phoneNumberMap = new HashMap<>(); phoneNumberNode.fields().forEachRemaining(field -> phoneNumberMap.put(field.getKey(), field.getValue().asText())); phoneNumbersList.add(phoneNumberMap); }); return phoneNumbersList; } }
The PhoneNumbersDeserializer
class extends JsonDeserializer<List<Map<String, Object>>>
and overrides the deserialize
method to provide custom deserialization logic for the phoneNumbers
field. It uses an ObjectMapper
to read the JSON node and iterates over the elements of the phoneNumbers
array using the forEach
method.
For each phone number node, it iterates over the fields and populates a HashMap
with the phone number details. These maps are then added to a List<Map<String, Object>>
, ensuring that the phoneNumbers
field in the JSON is correctly mapped to a list of maps in the Person
class.
These custom deserializers ensure that the nested JSON fields will be correctly mapped to the corresponding Java data structures
Register the Custom Deserializer
To use the custom deserializers, register them with the ObjectMapper
.
public class CustomDeserializationExample { public static void main(String[] args) { ObjectMapper mapper = new ObjectMapper(); try { JsonNode jsonNode = mapper.readTree(new FileReader("src/main/resources/data.json")); SimpleModule module = new SimpleModule(); module.addDeserializer(Map.class, new AddressDeserializer()); module.addDeserializer(List.class, new PhoneNumbersDeserializer()); mapper.registerModule(module); Person person = mapper.readValue(jsonNode.toString(), Person.class); System.out.println("Name: " + person.getName()); System.out.println("Age: " + person.getAge()); System.out.println("Address: " + person.getAddress()); System.out.println("Phone Numbers: " + person.getPhoneNumbers()); } catch (IOException e) { System.out.println(" " + e); } } }
Here, we initialize an ObjectMapper
and set up a SimpleModule
to register AddressDeserializer
and PhoneNumbersDeserializer
. These custom deserializers handle the nested address
and phoneNumbers
fields. The module is registered with the ObjectMapper
, which then converts the JsonNode
to a Person
object.
Test the Deserialization
Run the Main
class. The output should display the deserialized Person
object, including the address and phone numbers.
Output
4. Conclusion
In this article, we explored various methods for converting Jackson JsonNode
objects into typed Java collections. We discussed using ObjectMapper
with TypeReference
and readValue
methods to transform JSON data into List
and Map
structures. Additionally, we demonstrated how to manually traverse JsonNode
and employ custom deserializers for more complex scenarios. By utilizing these techniques, we can efficiently manage and manipulate JSON data in Java applications, leveraging the capabilities of the Jackson library.
5. Download the Source Code
This article explores how to convert Jackson JsonNode objects to Java collection type.
You can download the full source code of this example here: Java jackson Jsonnode collection