DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Related

  • How Spring and Hibernate Simplify Web and Database Management
  • Graceful Shutdown: Spring Framework vs Golang Web Services
  • Enabling Behavior-Driven Service Discovery: A Lightweight Approach to Augment Java Factory Design Pattern
  • Choosing the Right Caching Strategy

Trending

  • Securing the Future: Best Practices for Privacy and Data Governance in LLMOps
  • Driving DevOps With Smart, Scalable Testing
  • Memory Leak Due to Time-Taking finalize() Method
  • System Coexistence: Bridging Legacy and Modern Architecture
  1. DZone
  2. Coding
  3. Frameworks
  4. Consuming Variable Responses in Jackson

Consuming Variable Responses in Jackson

In this article, we demonstrate using the Jackson Library for the same solution used previously to handle variable responses in Gson.

By 
Yavuz Tas user avatar
Yavuz Tas
·
Jan. 07, 20 · Tutorial
Likes (7)
Comment
Save
Tweet
Share
15.1K Views

Join the DZone community and get the full member experience.

Join For Free

 Check out this post where we further demonstrate using the Jackson Library to handle variable responses.


In addition to being another popular library of JSON in Java, the Jackson Library is another well-known library, primarily recognized for its ability to offer deep customization in an opinionated way.

In this article, we are going to show an alternate way to use the Jackson Library for the same solution that was demonstrated in this post on how to handle variable responses in Gson.

You may also like: Processing JSON With Jackson

Adding Dependencies

Before we start, we need to add the Maven dependencies into our project'spom.xml:

XML
 




x
11


 
1
<dependency>
2
    <groupId>com.fasterxml.jackson.core</groupId>
3
    <artifactId>jackson-core</artifactId>
4
    <version>2.9.9</version>
5
</dependency>
6
 
           
7
<dependency>
8
    <groupId>com.fasterxml.jackson.core</groupId>
9
    <artifactId>jackson-databind</artifactId>
10
    <version>2.9.9</version>
11
</dependency>



We should note that if we already use Spring Boot and Spring Web Starter module enabled in our project, then we simply don't need to add any extra dependencies for Jackson.

Sample Response

Let's use this sample API response:

JSON
 




xxxxxxxxxx
1


 
1
{
2
    "id": 1,
3
    "name": "sample article",
4
    "comments": [
5
        {"id":1, "text":"some comment text"},
6
        {"id":2, "text":"some another comment text"}
7
    ]
8
}



Also, the response changes from Array to Object notation when there is only a single comment:

JSON
 




xxxxxxxxxx
1


 
1
{
2
    "id": 1,
3
    "name": "sample article",
4
    "comments": {"id":1, "text":"some comment text"}
5
}



Response Models

We'll also use the same models from the previous article:

Java
 




xxxxxxxxxx
1
21


 
1
public class ArticleModel {
2
 
           
3
    private Long id;
4
 
           
5
    private String name;
6
 
           
7
    private List<CommentModel> comments;
8
 
           
9
    // standard getters and setters
10
 
           
11
}
12
 
           
13
public class CommentModel {
14
 
           
15
    private Long id;
16
 
           
17
    private String text;
18
 
           
19
    // standard getters and setters
20
 
           
21
}



Consuming the Response

We defined a collection type for the comment field as List<CommentModel>. Thus, we can map multiple comments into a single field automatically because Jackson already does the heavy lifting for us:

Java
 




xxxxxxxxxx
1
24


 
1
String jsonString = "{
2
    "id": 1,
3
    "name": "sample article",
4
    "comments": [
5
        {"id":1, "text":"some comment text"},
6
        {"id":2, "text":"some another comment text"}
7
    ]
8
}";
9
 
           
10
ObjectMapper mapper = new ObjectMapper();
11
ArticleModel model = mapper.readValue(this.mockResponseMultiValue, ArticleModel.class);
12
 
           
13
Assert.assertEquals(ArticleModel.class, model.getClass());
14
Assert.assertEquals(model.getComments().getClass(), CommentList.class);
15
Assert.assertEquals(model.getComments().get(0).getClass(), CommentModel.class);
16
 
           
17
Assert.assertEquals(1, model.getId().longValue());
18
Assert.assertEquals("sample article", model.getName());
19
 
           
20
Assert.assertEquals(2, model.getComments().size());
21
Assert.assertEquals(1, model.getComments().get(0).getId().longValue());
22
Assert.assertEquals("some comment text", model.getComments().get(0).getText());
23
Assert.assertEquals(2, model.getComments().get(1).getId().longValue());
24
Assert.assertEquals("some another comment text", model.getComments().get(1).getText());



Certainly, this configuration resolves the multiple comment values unless the response changes to a single value structure. Otherwise, it simply doesn't work.

Consequently, we need to define a custom deserializer to handle both single and multiple values at once.

Custom Deserializers for Jackson

 StdDeserializer is an abstract type and also the base class for common deserializers in Jackson.

Therefore, to implement a custom deserialization behavior, we will create an implementation of  StdDeserializer. This behavior will be responsible for adding any single value to the list as the same as multiple values are being added automatically by Jackson.

So, let's create CommentListDeserializer, which inherits the base class StdDeserializer:

Java
 




xxxxxxxxxx
1
18


 
1
public class CommentListDeserializer extends StdDeserializer<List> {
2
 
           
3
    public CommentListDeserializer() {
4
        this(null);
5
    }
6
 
           
7
    private CommentListDeserializer(JavaType valueType) {
8
        super(valueType);
9
    }
10
 
           
11
    @Override
12
    public List deserialize(JsonParser p, DeserializationContext ctxt)
13
      throws IOException, JsonProcessingException {
14
 
           
15
        return null;
16
    }
17
 
           
18
}



We need a TypeReference for Jackson to specify the Collection type and the type of elements in which are involved as well.

So, let's define a constant for a type of  List<CommentModel>:

Java
 




xxxxxxxxxx
1


 
1
private static final TypeReference<List<CommentModel>> LIST_OF_COMMENTS_TYPE =
2
  new TypeReference<List<CommentModel>>() {};



Next, we implement the deserialize method to come up with the expected behavior:

Java
 




xxxxxxxxxx
1
20


 
1
@Override
2
public List deserialize(JsonParser p, DeserializationContext ctxt)
3
  throws IOException, JsonProcessingException {
4
 
           
5
    List<CommentModel> commentList = new ArrayList<>();
6
 
           
7
    JsonToken token = p.currentToken();
8
 
           
9
    if (JsonToken.START_ARRAY.equals(token)) {
10
        List<CommentModel> listOfArticles = p.readValueAs(LIST_OF_COMMENTS_TYPE);
11
        commentList.addAll(listOfArticles);
12
    }
13
 
           
14
    if (JsonToken.START_OBJECT.equals(token)) {
15
        CommentModel article = p.readValueAs(CommentModel.class);
16
        commentList.add(article);
17
    }
18
 
           
19
    return commentList;
20
}



Deserialization Features in Jackson

Although writing a custom deserializer offers us great flexibility, there are lots of features bundled in the Jackson Library for conversions. Jackson's predefined features provide practical ways to customize both serialization and deserialization.

Hopefully, the ACCEPT_SINGLE_VALUE_AS_ARRAY feature does the exact job for our use case. Certainly, we could use it for the sake of simplicity:

Java
 




xxxxxxxxxx
1


 
1
ObjectMapper mapper = new ObjectMapper();
2
mapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);



Also, an alternate way is to configure the @JsonFormat annotation. In our example, we'll go with the  @JsonFormat annotation to set the ACCEPT_SINGLE_VALUE_AS_ARRAY feature.

Let's remove our custom deserializer and add a simple @JsonFormat annotation to our comments field in ArticleModel:

Java
 




xxxxxxxxxx
1


 
1
@JsonFormat(with = Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
2
private List<CommentModel> comments;



Thus, this will automatically add any single value of CommentModel to our list as if it were already inside an array. Unless we have any further requirements, using Jackson features can be the most elegant solution for our use-case.

We can always have a chance to check the full list of features from the source code.

Going Further With Generic Deserializers

We can write generic deserializers for different requirements that we couldn't handle by predefined features. Besides, for the benefit of reusability, we can go further to refactor our code not to create new deserializers for each element type.

So, we'll implement a new class as SingleAwareListDeserializer to handle the deserialization in a more customizable way:

Java
 




xxxxxxxxxx
1
51


 
1
public class SingleAwareListDeserializer extends StdDeserializer<List> 
2
  implements ContextualDeserializer {
3
 
           
4
    private Class<?> contentClassType;
5
 
           
6
    public SingleAwareListDeserializer() {
7
        this(null);
8
    }
9
 
           
10
    private SingleAwareListDeserializer(JavaType valueType) {
11
        super(valueType);
12
    }
13
 
           
14
    @Override
15
    public JsonDeserializer<?> createContextual(
16
      DeserializationContext ctxt, BeanProperty property) throws JsonMappingException {
17
        // we use ContextualDeserializer to obtain content class type
18
        contentClassType = property.getType().getContentType().getRawClass();
19
        return this;
20
    }
21
 
           
22
    @Override
23
    public List deserialize(JsonParser p, DeserializationContext ctxt) 
24
      throws IOException, JsonProcessingException {
25
 
           
26
        List list = new ArrayList<>();
27
 
           
28
        JsonToken token = p.currentToken();
29
        // if token is array type then perform object deserialization to each element element
30
        if (JsonToken.START_ARRAY.equals(token)) {
31
            while (p.nextToken() != null) {
32
                if (JsonToken.START_OBJECT.equals(p.currentToken())) {
33
                    list.add(deserializeObject(p));
34
                }
35
            }
36
        }
37
 
           
38
        // if token is object type
39
        if (JsonToken.START_OBJECT.equals(token)) {
40
            list.add(deserializeObject(p));
41
        }
42
 
           
43
        return list;
44
    }
45
 
           
46
    private Object deserializeObject(JsonParser p) throws IOException {
47
        // just use jackson default object deserializer by using element type
48
        return p.readValueAs(contentClassType);
49
    }
50
 
           
51
}



Now, we can use the same deserializer for different types as well.

Certainly, we should notice the challenge here, which is about how to obtain the class type of the element inside our collection. Without the element's type information, it is not possible for Jackson to initialize the concrete type but only LinkedHashMap.

However, there is a workaround to obtain type-related information inside our deserializer.

We used the ContextualDeserializer interface to inform Jackson that we need information about the bean we are dealing with. Thus, an instance of BeanProperty is automatically injected and we obtained the class type of the collection element.

Finally, to use our generic deserializer, let's change our comments field:

Java
 




xxxxxxxxxx
1


 
1
@JsonDeserialize(using = SingleAwareListDeserializer.class)
2
private List<CommentModel> comments;



As a result, we can provide the same behavior by the  SingleAwareListDeserializer generically for all types as well.

Finally

In this tutorial, we learned how to handle variable responses in Jackson by using the builtin features and custom deserializers as well.

All the source code for the examples shown in this tutorial are available over on GitHub.

Further Reading

 Processing JSON With Jackson

Solving the XML Problem With Jackson

Jackson Annotations for JSON (Part 1): Serialization and Deserialization

Jackson (API) Spring Framework Java (programming language)

Published at DZone with permission of Yavuz Tas. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • How Spring and Hibernate Simplify Web and Database Management
  • Graceful Shutdown: Spring Framework vs Golang Web Services
  • Enabling Behavior-Driven Service Discovery: A Lightweight Approach to Augment Java Factory Design Pattern
  • Choosing the Right Caching Strategy

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!