Customizing the JSON output

Using our tools we are able to easily see the request generated by our server. It is huge. By default, Jackson, the JSON serialization library used by Spring Boot, will serialize everything that is accessible with a getter method.

We would like something lighter, such as this:

{
  "text": "original text",
  "user": "some_dude",
  "profileImageUrl": "url",
  "lang": "en",
  "date": 2015-04-15T20:18:55,
  "retweetCount": 42
}

The easiest way to customize which fields will be serialized is by adding annotations to our beans. You can either use the @JsonIgnoreProperties annotation at the class level to ignore a set of properties or add @JsonIgnore on the getters of the properties you wish to ignore.

In our case, the Tweet class is not one of our own. It is part of Spring Social Twitter, and we do not have the ability to annotate it.

Using the model classes directly for serialization is rarely a good option. It would tie your model to your serialization library, which should remain an implementation detail.

When dealing with unmodifiable code, Jackson provides two options:

  • Creating a new class dedicated to serialization.
  • Using mixins, which are simple classes that will be linked to your model. These will be declared in your code and can be annotated with any Jackson annotation.

Since we only need to perform some simple transformation on the fields of our model (a lot of hiding and a little renaming), we could opt for the mixins.

It's a good, non-invasive way to rename and exclude fields on the fly with a simple class or interface.

Another option to specify subsets of fields used in different parts of the application is to annotate them with the @JsonView annotation. This won't be covered in this chapter, but I encourage you to check out this excellent blog post https://spring.io/blog/2014/12/02/latest-jackson-integration-improvements-in-spring.

We want to be in control of the output of our APIs, so let's just create a new class called LightTweet that can be constructed from a tweet:

package masterSpringMvc.search;

import org.springframework.social.twitter.api.Tweet;
import org.springframework.social.twitter.api.TwitterProfile;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;

public class LightTweet {
    private String profileImageUrl;
    private String user;
    private String text;
    private LocalDateTime date;
    private String lang;
    private Integer retweetCount;

    public LightTweet(String text) {
        this.text = text;
    }

    public static LightTweet ofTweet(Tweet tweet) {
        LightTweet lightTweet = new LightTweet(tweet.getText());
        Date createdAt = tweet.getCreatedAt();
        if (createdAt != null) {
            lightTweet.date = LocalDateTime.ofInstant(createdAt.toInstant(), ZoneId.systemDefault());
        }
        TwitterProfile tweetUser = tweet.getUser();
        if (tweetUser != null) {
            lightTweet.user = tweetUser.getName();
            lightTweet.profileImageUrl = tweetUser.getProfileImageUrl();
        }
        lightTweet.lang = tweet.getLanguageCode();
        lightTweet.retweetCount = tweet.getRetweetCount();
        return lightTweet;
    }

  // don't forget to generate getters
  // They are used by Jackson to serialize objects
}

We now need to make our SearchService class return the LightTweets class instead of tweets:

    public List<LightTweet> search(String searchType, List<String> keywords) {
        List<SearchParameters> searches = keywords.stream()
                .map(taste -> createSearchParam(searchType, taste))
                .collect(Collectors.toList());

        List<LightTweet> results = searches.stream()
                .map(params -> twitter.searchOperations().search(params))
                .flatMap(searchResults -> searchResults.getTweets().stream())
                .map(LightTweet::ofTweet)
                .collect(Collectors.toList());

        return results;
    }

This will impact the return type of the SearchApiController class as well as the tweets model attribute in the SearchController class. Make the necessary modification in those two classes.

We also need to change the code of the resultPage.html file because some properties changed (we no longer have a nested user property):

<ul class="collection">
    <li class="collection-item avatar" th:each="tweet : ${tweets}">
        <img th:src="${tweet.profileImageUrl}" alt="" class="circle"/>
        <span class="title" th:text="${tweet.user}">Username</span>

        <p th:text="${tweet.text}">Tweet message</p>
    </li>
</ul>

We're almost done. If you restart your application and go to http://localhost:8080/api/search/mixed;keywords=springFramework, you'll see that the date format is not the one we expected:

Customizing the JSON output

That's because Jackson doesn't have built-in support for JSR-310 dates. Luckily, this is easy to fix. Simply add the following library to the dependencies in the build.gradle file:

compile 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'

This indeed changes the date format, but it now outputs an array instead of a formatted date.

To change that, we need to understand what the library did. It includes a new Jackson module called JSR-310 Module. A Jackson module is an extension point to customize serialization and deserialization. This one will automatically be registered by Spring Boot at startup in the JacksonAutoConfiguration class, which will create a default Jackson ObjectMapper method with support for well-known modules.

We can see that the former module adds a bunch of serializers and deserializers for all the new classes defined in JSR-310. This will try to convert every date to an ISO format, whenever possible. See https://github.com/FasterXML/jackson-datatype-jsr310.

If we take a closer look at LocalDateTimeSerializer, for instance, we can see that it actually has two modes and can switch between the two with a serialization feature called WRITE_DATES_AS_TIMESTAMPS.

To define this property, we need to customize Spring's default object mapper. As we can gather from looking at the auto configuration, Spring MVC provides a utility class to create the ObjectMapper method that we can use. Add the following bean to your WebConfiguration class:

@Bean
@Primary
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
   ObjectMapper objectMapper = builder.createXmlMapper(false).build();
   objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
   return objectMapper;
}

This time, we are done and the dates are properly formatted, as you can see here:

Customizing the JSON output
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset