Codersee

@JsonView With Spring Boot and Kotlin

A featured image for category: Spring

1. Introduction

In this article, I would like to show you the Jackson @JsonView annotation and how to use it, when working Spring Boot and Kotlin.

@JsonView is an annotation used to instruct the Jackson mapper, whether the annotated field is a part of the view and should be serialized, or not. And I know, it might sound a bit complicated, but in the next few minutes, I will prove that it is only a first impression.

2. Prepare Spring Boot Project

2.1. Setup

But before we dive into the @JsonView, let’s configure our sandbox Spring Boot project first. As always, I highly encourage you to use the Spring Initializr page to do it. Just to be on the same page, I am using the following settings:

  • Language: Kotlin
  • Spring Boot version: 2.7.2
  • Java: 17
  • Dependencies: Spring Web
  • Jackson version: 2.13.3

Moreover, you can find the link to the repository with all examples in the Summary section for this article.

2.2. Prepare DTO

With that being done, let’s create a data class called ArticleDto:

data class ArticleDto(
  val id: Long,
  val title: String,
  val category: String,
  val content: String,
  val views: Long,
  val likes: Long
)

As we can see, this class is responsible for transferring the data about an article on a blog.

2.2. Implement Controller Class

Following, let’s introduce the ArticleController to our codebase:

@RestController
@RequestMapping("/articles")
class ArticleController {

  private val articles = mapOf(
    1L to ArticleDto(
      1L, "Article 1", "Spring Framework",
      "Content 1", 1000, 30
    ),
    2L to ArticleDto(
      2L, "Article 2", "Kotlin",
      "Content 2", 5000, 54
    )
  )

  @GetMapping
  fun getAllArticles(): List<ArticleDto> =
    articles.values.toList()

  @GetMapping("/{id}")
  fun getArticleDetails(@PathVariable id: Long): ArticleDto? =
    articles[id]

  @GetMapping("/{id}/analytics")
  fun getArticleAnalytics(@PathVariable id: Long): ArticleDto? =
    articles[id]
}

As can be seen, in our example, we exposed 3 different endpoints:

  • /articles – used to populate the list of articles
  • /articles/{id} – to obtain the details of an article by its identifier
  • /articles/{id}/analytics – to obtain the analytics data

2.3. Check Endpoints

With that being done, let’s quickly check how our Spring Boot app behaves without @JsonView.

Let’s query the /articles endpoint:

#Result: 

[
  {
    "id": 1,
    "title": "Article 1",
    "category": "Spring Framework",
    "content": "Content 1",
    "views": 1000,
    "likes": 30
  },
  {
    "id": 2,
    "title": "Article 2",
    "category": "Kotlin",
    "content": "Content 2",
    "views": 5000,
    "likes": 54
  }
]

Everything is working fine and as a result, we receive a list of our test data.

On the other hand, when we query either the /articles/{id}, or the /articles/{id}/analytics, we see the following:

#Result: 

{
  "id": 1,
  "title": "Article 1",
  "category": "Spring Framework",
  "content": "Content 1",
  "views": 1000,
  "likes": 30
}

At this point, both endpoints work in the same manner, but we will see how to change it in the next chapters.

3. How Does the @JsonView Help Me In Spring Boot Then?

So, as I mentioned earlier- these 3 endpoints were created to return the data about all articles, details of the article, and analytics.

As we can clearly see, although they are working, we don’t need all of the data every time and we should limit the response payload:

  • /articles – in the case of the list, the information about identifier, title, and category should be sufficient
  • /articles/{id} – when querying the details, we would like to get all the information
  • /articles/{id}/analytics – we would like to use this one to obtain the views and likes information only

Knowing these requirements we could introduce 3 different DTOs and implement appropriate mappers. Although this solution will work, managing multiple DTOs and mappers may become troublesome at some point. Additionally, in case of any change, we will have to mimic it across each one.

Nevertheless, the @JsonView is a great answer to this problem and in the next chapters, I will show you how to introduce it to our Spring Boot app.

4. Simple @JsonView

With all of that being said, we can finally start working with the @JsonView.

As the first step, let’s create a new Views class with two inner interfaces:

class Views {
  interface List
  interface Analytics
}

Nextly, let’s modify our DTO a bit:

data class ArticleDto(
  @field:JsonView(Views.List::class)
  val id: Long,
  @field:JsonView(Views.List::class)
  val title: String,
  @field:JsonView(Views.List::class)
  val category: String,
  val content: String,
  @field:JsonView(Views.Analytics::class)
  val views: Long,
  @field:JsonView(Views.Analytics::class)
  val likes: Long
)

As we can see, the id, title, and category fields are a part of the List view. On the other hand, the views and likes properties belong to Analytics. If we test our endpoints now, we might be surprised that nothing changed, but that’s the desired state.

To make use of the @JsonView in Spring Boot, we have to annotate our endpoints, as well:

@GetMapping
@JsonView(Views.List::class)
fun getAllArticles(): List<ArticleDto> =
  articles.values.toList()

@JsonView(Views.Analytics::class)
@GetMapping("/{id}/analytics")
fun getArticleAnalytics(@PathVariable id: Long): ArticleDto? =
  articles[id]

With that being done, let’s test these two endpoints once again:

# List view result: 

[
  {
    "id": 1,
    "title": "Article 1",
    "category": "Spring Framework"
  },
  {
    "id": 2,
    "title": "Article 2",
    "category": "Kotlin"
  }
]

# Analytics view result: 

{
  "views": 1000,
  "likes": 30
}

And this time, response payloads are limited to the desired fields only. We don’t have to test the details endpoint, because we already know that nothing changes unless we mark the endpoint with @JsonView annotation.

5. Default View Inclusion

As the next step, let’s take a second to understand the default view inclusion concept.

To do so, let’s add a Config class to our project:

@Configuration
class Config {

  @Bean
  fun objectMapper(): ObjectMapper =
    JsonMapper.builder()
      .enable(MapperFeature.DEFAULT_VIEW_INCLUSION) //don't needed here, enabled by default
      .build()

}

If we specify our bean explicitly, the DEFAULT_VIEW_INCLUSION feature will be enabled by default.

This means, that fields not annotated with @JsonView, will be still serialized. To better visualize it, let’s re-test our endpoints:

# List view result: 

[
  {
    "id": 1,
    "title": "Article 1",
    "category": "Spring Framework",
    "content": "Content 1"
  },
  {
    "id": 2,
    "title": "Article 2",
    "category": "Kotlin",
    "content": "Content 2"
  }
]

# Analytics view result: 

{
  "content": "Content 1",
  "views": 1000,
  "likes": 30
}

As we can see, in both cases non-annotated content field is returned. Sometimes this behavior might be useful, but in some cases, it might cause us trouble, so we have to be aware of this behavior.

6. Extending Views

As the last thing, let’s see another great feature- extending views.

Let’s imagine that we would like to introduce a new endpoint, which will return fields of both List and Analytics views. Let’s call it ListAndAnalytics:

class Views {
  interface List
  interface Analytics
  interface ListAndAnalytics
}

As the next step, we could add our new view everywhere, where List or Analytics is presented:

data class ArticleDto(
  @field:JsonView(Views.List::class, Views.ListAndAnalytics::class)
  val id: Long,
  @field:JsonView(Views.List::class, Views.ListAndAnalytics::class)
  val title: String,
  @field:JsonView(Views.List::class, Views.ListAndAnalytics::class)
  val category: String,
  val content: String,
  @field:JsonView(Views.Analytics::class, Views.ListAndAnalytics::class)
  val views: Long,
  @field:JsonView(Views.Analytics::class, Views.ListAndAnalytics::class)
  val likes: Long
)

And although this works, the code becomes much less readable. (and imagine what will happen if we decide to add even more views)

We can simply avoid that by making our new interface extending the two others:

class Views {
  interface List
  interface Analytics
  interface ListAndAnalytics : List, Analytics
}

And annotate our endpoint appropriately:

@JsonView(Views.ListAndAnalytics::class)
@GetMapping("/{id}/list-and-analytics")
fun getArticleListAndAnalytics(@PathVariable id: Long): ArticleDto? =
  articles[id]

Which, when queried, will produce the following result:

# List and analytics view result: 

{
  "id": 1,
  "title": "Article 1",
  "category": "Spring Framework",
  "views": 1000,
  "likes": 30
}

As we can see, with this simple approach we can easily create a hierarchy of views in our codebase.

5. @JsonView with Spring Boot Summary

And that would be all for this article on what the @JsonView annotation brings to the table when working with Spring Boot. I hope that after reading this tutorial you will be able to apply it to your code and save your precious time during the development

Pssst… if you would like to contribute to Codersee and support the creation of content, you can “buy me a coffee. Have a great day! 🙂

Also, you can find the source code with examples in this GitHub repository (and other articles related to Jackson on my blog right here).

Leave a Reply

Your email address will not be published.

Categories

Author

Hi there! 👋

Hi there! 👋

My name is Piotr and I've created Codersee to share my knowledge about Kotlin, Spring Framework, and other related topics through practical, step-by-step guides. Always eager to chat and exchange knowledge.

Buy Me a Coffee at ko-fi.com

Join the FREE weekly newsletter and get two free eBooks:

Image shows the covers of free ebooks accessible for newsletter subscribers.

You may opt out any time. Terms of Use and Privacy Policy.

To make Codersee work, we log user data. By using our site, you agree to our Privacy Policy and Terms of Use.