Map Subset of JSON via Jackson
1. Introduction
JavaScript Object Notation (JSON) is a text-based data format and widely used in the APIs for exchanging data between a client and server. The Jackson library from FasterXML is the most popular library for serializing Java objects to JSON and vice-versa. Sometimes, the JSON data returned from the server is a complex data structure as it considers all clients’ requirements. However, a client may only need a subset of the JSON data. This is similar to creating a database view based on tables. In this example, I will demonstrate how to map a subset of JSON using Jackson libraries.
2. Project Setup
In this step, I will set up a maven project with Jackson libraries to read three JSON files and map a subset of JSON into a Java POJO.
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.zheng.demo</groupId> <artifactId>jackson-subset</artifactId> <version>0.0.1-SNAPSHOT</version> <dependencies> <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.17.1</version> </dependency> <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-annotations --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.17.1</version> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.10.2</version> <scope>test</scope> </dependency> </dependencies> </project>
Here is the screenshot of the project setup.
Note: There are two sets of Customer
and Order
POJOs which map to the desired subset of the JSON fields. The ones under org.zheng.demo.data
package do not have the root element configured but the ones under org.zheng.demo.type
package have the root element configured.
3. JSON Files
In this step, I will create three JSON files that will be used at step 4 and 5.
customer.json
– this JSON contains a customer and its two orders.customerWithWrapRoot.json
– this JSON contains the default root from Jackson – the simple class name.customWithJsonType.json
– this JSON contains a customized root from Jackson with@JsonTypeName
annotation.
3.1 Customer JSON File
The customer.json
file contains a customer and its two orders.
customer.json
{ "custTag" : "major", "email" : "test@test.com", "id" : 30, "name" : "Zheng", "orders" : [ { "productName" : "test", "quantity" : 10, "ignoreOrder":"should not be found in POJO" } , { "productName" : "test", "quantity" : 1, "ignoreOrder":"should not be found in POJO" } ], "phone":"636-123-2345", "balance":1234.56, "rewardPoint":100 }
- Note: for this example, the client only wants to map the highlighted fields:
custTag
,email
,id
,name
, andorder
‘sproductName
andquantity
.
3.2 Custom with Wrapp_Root JSON File
The customerWithWrapRoot.json
contains the root element with its simple class name – Customer
.
customerWithWrapRoot.json
{ "Customer": { "custTag": "major", "email": "test@test.com", "id": 30, "name": "Zheng", "unknowCustom": "not showing in this project", "orders": [ { "productName": "test", "unknowOrder": "not showing in this project", "quantity": 10 }, { "productName": "test", "unknowOrder": "not showing in this project", "quantity": 10 } ], "phone":"636-123-2345", "balance":1234.56, "rewardPoint":100 } }
- Line 2: this JSON contains the default wrap_root:
Customer
.
3.3 Customer with Customized Root JSON File
The customerWithJsonType.json
contains the customized root element name – customer
.
customerWithJsonType.json
{ "customer": { "custTag": "major", "email": "test@test.com", "id": 30, "name": "Zheng", "orders": [ { "order": { "productName": "test", "unknowOrder": "not showing in this project", "quantity": 10 } }, { "order": { "productName": "test", "unknowOrder": "not showing in this project", "quantity": 10 } } ], "phone":"636-123-2345", "balance":1234.56, "rewardPoint":100 } }
- Line 2: this JSON contains the “
customer
” root node. - Line 9, 16: the
orders
node contains the “order
” root node.
4. Map Subset to Java Object
In this step, I will create two Java data objects annotated with @JsonIgnoreProperties(ignoreUnknown =true)
so they can be mapped to a subset of the JSON file created at step 3.1.
4.1 Customer Object
In this step, I will create a simple Customer
class which annotates with @JsonIgnoreProperties(ignoreUnknown = true).
This class contains five fields from a total of eight fields from Customer.json
file.
Customer.java
package org.zheng.demo.data; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @JsonIgnoreProperties(ignoreUnknown = true) public class Customer implements Serializable { private static final long serialVersionUID = 5963349342478710542L; private String custTag; private String email; private int id; private String name; private List<Order> orders; public Customer() { super(); } public Customer(String name, int id) { super(); this.id = id; this.name = name; } public void addOrder(Order order) { if (this.orders == null) { this.orders = new ArrayList<>(); } this.orders.add(order); } public String getCustTag() { return custTag; } public String getEmail() { return email; } public int getId() { return id; } public String getName() { return name; } public List<Order> getOrders() { return orders; } public void setCustTag(String custTag) { this.custTag = custTag; } public void setEmail(String email) { this.email = email; } public void setId(int id) { this.id = id; } public void setName(String name) { this.name = name; } public void setOrders(List<Order> orders) { this.orders = orders; } }
- Note: only
custTag
,email
,id
,name
, andorders
are mapped.
4.2 Order Object
In this step, I will create a simple Order.java
which maps two fields from a total of three fields from Customer.json
‘s orders
node.
Order.java
package org.zheng.demo.data; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @JsonIgnoreProperties(ignoreUnknown = true) public class Order { private String productName; private int quantity; public Order() { super(); } public Order(int quantity, String name) { super(); this.quantity = quantity; this.productName = name; } public String getProductName() { return productName; } public int getQuantity() { return quantity; } public void setProductName(String name) { this.productName = name; } public void setQuantity(int quantity) { this.quantity = quantity; } }
Note: only productName
and quantity
are mapped.
4.3 Customer & Order with Root
In this step, I will create another set of Customer
and Order
classes under the org.zheng.demo.type
package.
The Customer
class has a similar data structure as defined at step 4.1 except with the @JsonTypeName
annotation and has an extra field: rewardPoint
.
Customer.java
package org.zheng.demo.type; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.annotation.JsonTypeInfo.As; import com.fasterxml.jackson.annotation.JsonTypeInfo.Id; @JsonIgnoreProperties(ignoreUnknown = true) @JsonTypeName("customer") @JsonTypeInfo(include=As.WRAPPER_OBJECT, use=Id.NAME) public class Customer implements Serializable { private static final long serialVersionUID = 5963349342478710542L; private String custTag; private String email; private int id; private String name; private List<Order> orders; private int rewardPoint; public Customer() { super(); } public Customer(String name, int id) { super(); this.id = id; this.name = name; } public void addOrder(Order order) { if (this.orders == null) { this.orders = new ArrayList<>(); } this.orders.add(order); } public String getCustTag() { return custTag; } public String getEmail() { return email; } public int getId() { return id; } public String getName() { return name; } public List<Order> getOrders() { return orders; } public void setCustTag(String custTag) { this.custTag = custTag; } public void setEmail(String email) { this.email = email; } public void setId(int id) { this.id = id; } public void setName(String name) { this.name = name; } public void setOrders(List<Order> orders) { this.orders = orders; } public int getRewardPoint() { return rewardPoint; } public void setRewardPoint(int rewardPoint) { this.rewardPoint = rewardPoint; } }
The Order.java
class is same as the class defined at step 4.2 except the @JsonTypeName
annotation.
Order.java
package org.zheng.demo.type; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeInfo.As; import com.fasterxml.jackson.annotation.JsonTypeInfo.Id; import com.fasterxml.jackson.annotation.JsonTypeName; @JsonIgnoreProperties(ignoreUnknown = true) @JsonTypeName("order") @JsonTypeInfo(include=As.WRAPPER_OBJECT, use=Id.NAME) public class Order { private String productName; private int quantity; public Order() { super(); } public Order(int quantity, String name) { super(); this.quantity = quantity; this.productName = name; } public String getProductName() { return productName; } public int getQuantity() { return quantity; } public void setProductName(String name) { this.productName = name; } public void setQuantity(int quantity) { this.quantity = quantity; } }
5. Demo How to Map a Subset of Json Using Jackson
5.1 Read via JsonNode
In this step, I will create a Junit test class Jackson_Node_Test
.java which utilizes the JsonNode
tree model to parse the JSON and extract the subset of fields dynamically.
Jackson_Node_Test.java
package org.zheng.demo; import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.Test; import org.zheng.demo.data.Customer; import org.zheng.demo.data.Order; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; class Jackson_Node_Test { @Test void test_readJsonFromFile_via_readTree() { ObjectMapper ob = new ObjectMapper(); File jsonFile = new File("src/test/resources/customer.json"); try { JsonNode jsonNodes = ob.readTree(jsonFile); String name = jsonNodes.get("name").asText(); int id = jsonNodes.get("id").asInt(); Customer cust = new Customer(name, id); cust.setCustTag(jsonNodes.get("custTag").asText()); cust.setEmail(jsonNodes.get("email").asText()); List<Order> orders = new ArrayList<>(); cust.setOrders(orders); JsonNode ordersNode = jsonNodes.get("orders"); if (ordersNode.isArray()) { for (JsonNode orderNode : ordersNode) { JsonNode nameNode = orderNode.get("productName"); JsonNode quantityNode = orderNode.get("quantity"); orders.add(new Order(quantityNode.asInt(), nameNode.asText())); } } assertEquals("major", cust.getCustTag()); assertEquals("test@test.com", cust.getEmail()); assertEquals("Zheng", cust.getName()); assertEquals(30, cust.getId()); assertEquals(2, cust.getOrders().size()); assertEquals("test", cust.getOrders().get(0).getProductName()); System.out.println(ob.writerWithDefaultPrettyPrinter().writeValueAsString(cust)); } catch (IOException e) { e.printStackTrace(); } } }
- Line 22: create a file object from the
src/test/resources/customer.json
. - Line 24: use the
objectMapper.readTree
to obtain theJsonNode
. - Line 26,27,28: parse the customer’s
id
andname
data fromJsonNode
and create acustomer
object. - Line 31, 32: parse the
custTag
andemail
data fromJsonNode
. - Line 36-41: parse the
orders
fromJsonNode
.
Execute this Junit test – test_readJsonFromFile_via_readTree
and capture the output.
test_readJsonFromFile_via_readTree output
{ "custTag" : "major", "email" : "test@test.com", "id" : 30, "name" : "Zheng", "orders" : [ { "productName" : "test", "quantity" : 10 }, { "productName" : "test", "quantity" : 1 } ] }
Note: as you see, the mapped customer object contains a subset of the original customer.json
file.
5.2 Ignore Unknown Properties
In this step, I will create a Junit test class JacksonTest
which uses @JsonIgnoreProperties(ignoreUnknown = true)
and ObjectMapper
to map a subset of JSON fields based on the Java POJO. There are two tests:
test_readJsonFromFile_via
– read the JSON from thecustomer.json
file and map a subset intoorg.zheng.demo.data.Customer
.test_readFile_withCustomizedRoot
– read the JSON from thecustomerWithJsonType
file and map a subset intoorg.zheng.demo.type.Customer
.
JacksonTest.java
package org.zheng.demo; import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.File; import java.io.IOException; import java.io.InputStream; import org.junit.jupiter.api.Test; import org.zheng.demo.data.Customer; import com.fasterxml.jackson.core.exc.StreamReadException; import com.fasterxml.jackson.databind.DatabindException; import com.fasterxml.jackson.databind.ObjectMapper; class JacksonTest { private ObjectMapper ob = new ObjectMapper(); @Test void test_readFile_withCustomizedRoot() { File jsonFile = new File("src/test/resources/customerWithJsonType.json"); try { org.zheng.demo.type.Customer readCust = ob.readValue(jsonFile, org.zheng.demo.type.Customer.class); assertEquals("major", readCust.getCustTag()); assertEquals("test@test.com", readCust.getEmail()); assertEquals("Zheng", readCust.getName()); assertEquals(30, readCust.getId()); assertEquals(2, readCust.getOrders().size()); assertEquals("test", readCust.getOrders().get(0).getProductName()); String jsonStr = ob.writerWithDefaultPrettyPrinter().writeValueAsString(readCust); System.out.println(jsonStr); } catch (IOException e) { e.printStackTrace(); } } @Test void test_readJsonFromFile_via() throws StreamReadException, DatabindException, IOException { try (InputStream inputStream = JacksonTest.class.getResourceAsStream("/customer.json")) { if (inputStream == null) { System.out.println("File not found"); return; } Customer cust = ob.readValue(inputStream, Customer.class); assertEquals("major", cust.getCustTag()); assertEquals("test@test.com", cust.getEmail()); assertEquals("Zheng", cust.getName()); assertEquals(30, cust.getId()); assertEquals(2, cust.getOrders().size()); assertEquals("test", cust.getOrders().get(0).getProductName()); String jsonStr = ob.writerWithDefaultPrettyPrinter().writeValueAsString(cust); System.out.println(jsonStr); } catch (IOException e) { e.printStackTrace(); } } }
- Line 22: read the JSON file from
"src/test/resources/customerWithJsonType.json".
- Line 25: use the
org.zheng.demo.type.Customer
class when utilizingobjectMapper.readValue
method. - Line 47: read the JSON file from
"/customer.json"
- Line 53: use the
org.zheng.demo.data.Customer
class when callingobjectMapper.readValue
method. - Line 34, 62: print out the subset mapped objects.
Execute this Junit test and capture the output.
test_readJsonFromFile_via output
{ "custTag" : "major", "email" : "test@test.com", "id" : 30, "name" : "Zheng", "orders" : [ { "productName" : "test", "quantity" : 10 }, { "productName" : "test", "quantity" : 1 } ] } { "customer" : { "custTag" : "major", "email" : "test@test.com", "id" : 30, "name" : "Zheng", "orders" : [ { "order" : { "productName" : "test", "quantity" : 10 } }, { "order" : { "productName" : "test", "quantity" : 10 } } ], "rewardPoint" : 100 } }
Note: as you see, only a subset of data are mapped. Also if the JSON has a customized root, then org.zheng.demo.type.Customer
is used.
5.3 Handle the Wrap_oot
In this step, I will create a Junit Test class Jackson_wrapRootTest
. It has two test methods that map a subset based on the default root.
test_readFile_withWrapRoot
– read a JSON string fromcustomerWithWrapRoot.json
file and map it withDeserializationFeature.UNWRAP_ROOT_VALUE
.test_write_read_withRoot
– verify the root is added to the JSON whenSerializationFeature.WRAP_ROOT_VALUE
is enabled.
Jackson_WrapRootTest.java
package org.zheng.demo; import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.Test; import org.zheng.demo.data.Customer; import org.zheng.demo.data.Order; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; class Jackson_WrapRootTest { private ObjectMapper ob = new ObjectMapper(); @Test void test_readFile_withWrapRoot() { File jsonFile = new File("src/test/resources/customerWithWrapRoot.json"); try { Customer readCust = ob.readerFor(Customer.class).with(DeserializationFeature.UNWRAP_ROOT_VALUE) .readValue(jsonFile); assertEquals("major", readCust.getCustTag()); assertEquals("test@test.com", readCust.getEmail()); assertEquals("Zheng", readCust.getName()); assertEquals(30, readCust.getId()); assertEquals(2, readCust.getOrders().size()); assertEquals("test", readCust.getOrders().get(0).getProductName()); } catch (IOException e) { e.printStackTrace(); } } @Test void test_write_read_withRoot() { Customer cust = new Customer("Zheng", 30); Order order = new Order(); cust.setEmail("test@test.com"); List orders = new ArrayList(); order.setProductName("test"); order.setQuantity(10); orders.add(order); orders.add(order); cust.setOrders(orders); try { String jsonString = ob.enable(SerializationFeature.WRAP_ROOT_VALUE).writeValueAsString(cust); System.out.println("Serialized JSON: " + jsonString); } catch (JsonProcessingException e) { e.printStackTrace(); } } }
- Line 25: read JSON from
customerWithWrapRoot.json
. - Line 28, 57: set
DeserializationFeature.UNWRAP_ROOT_VALUE
when reading andSerializationFeature.WRAP_ROOT_VALUE
when writing.
Execute this Junit test and capture the output.
Jackson_wrapRootTest output
Serialized JSON: {"Customer":{"custTag":null,"email":"test@test.com","id":30,"name":"Zheng","orders":[{"productName":"test","quantity":10},{"productName":"test","quantity":10}]}}
6. Conclusion
Sometimes, the JSON structure may contain more information than what is needed for a particular consumer. By mapping only a subset makes it simpler to work with. Processing and deserializing only the necessary parts with large JSON payload can significantly improve performance, simplify the logic, secure data, and reduce memory usage. In this example, I demonstrated how to map a subset of JSON using Jackson libraries with three examples.
- JSON file without any root.
- JSON file with the default wrap_root.
- JSON file with a customized root.
7. Download
This was an example of maven project which demonstarte how to map a subset of JSON file using Jackson libraries.
You can download the full source code of this example here: Map Subset of JSON via Jackson