Dec 14, 2017

Serializing Groovy Traits with Jackson

Working on a Groovy and Spring Boot project, we encountered a serialization issue on an object that implemented a Groovy trait. The object would be serialized correctly until the JsonFormat annotation was used on one of the fields in the trait that the object implemented. Once added, the property with that annotation was duplicated and one of those duplications contained the qualified class name!

Setup

Let’s look at an example. The BaseTrait below is a simple trait that has a few fields and a method.

trait BaseTrait {
    Long id
 
    OffsetDateTime lastUpdated
 
    def build(Long id, OffsetDateTime offsetDateTime){
        this.id = id
        this.lastUpdated = offsetDateTime
    }
 
}

The BasicObject implements that trait and contains another field.

class BasicObject implements BaseTrait{
 
    Long otherId
 
    BasicObject(Long id, OffsetDateTime offsetDateTime, Long otherId){
        build(id, offsetDateTime)
 
        this.otherId = otherId
    }
 
}

When a BasicObject is returned from a Spring controller and is serialized by Jackson, it returns the object with no unexpected behavior.

 
{
    "otherId": 2,
    "id": 1,
    "lastUpdated": 1512534032.694
}

Problem

We wanted the lastUpdated date object to be serialized to the ISO-8601 pattern, but didn’t want to set the default Jackson serialization behavior for all date objects in the application. Naturally, we chose to use the Jackson annotation JsonFormat with a specified pattern. Easy, right?

The result after adding the formatting annotation:

 
{
    "otherId": 2,
    "the_fully_qualified_package_BaseTrait__lastUpdated": "2017-12-05T10:20:32-0600",
    "id": 1,
    "lastUpdated": 1512534032.694
}

Wow! What happened? We just wanted to format the date into something human readable. However, when the JsonFormat annotation is added, the Jackson property auto detector registers both the field lastUpdated and the groovy method getLastUpdated() as separate fields to be serialized.

Solution

This problem can be solved with some simple annotations added to the trait.

First, to prevent Jackson from searching for any of the groovy generated getters/setters and mistaking them for wanted JSON fields, we need to use the JsonAutoDetect annotation on the trait. The annotation can be used to turn off any unwanted detection behaviors for that class.

Second, to solve the fully qualified name for the lastUpdated field, we need to use the JsonProperty annotation on all the fields in the trait. This extra annotation is needed due to the JsonAutoDetect annotation causing Jackson to print the fully qualified name for every trait property that is serialized.

The final code for the trait looks like the following:

 
@JsonAutoDetect(
        fieldVisibility = Visibility.ANY,
        getterVisibility = Visibility.NONE
)
trait BaseTrait {
    @JsonProperty('id')
    Long id
 
    @JsonProperty('lastUpdated')
    @JsonFormat(pattern = 'yyyy-MM-dd\'T\'hh:mm:ssZ')
    OffsetDateTime lastUpdated
 
    def build(Long id, OffsetDateTime offsetDateTime){
        this.id = id
        this.lastUpdated = offsetDateTime
    }
 
}

And that’s it! The BasicObject serializes as expected.

 
{
    "otherId": 2,
    "id": 1,
    "lastUpdated": "2017-12-05T10:20:32-0600"
}

About the Author

Chris Tosspon profile.

Chris Tosspon

Sr. Consultant

Chris is a developer passionate about solving complex problems. With his knowledge of cryptography and advanced hardware attacks, he pursued the life of a hardware hacker before turning his attention on software development and engineering.

Chris builds applications using Spring, Java, Groovy, and has experience with most of the popular Java testing frameworks, e.g. Spock, TestNG, JUnit.

Leave a Reply

Your email address will not be published. Required fields are marked *

Related Blog Posts
Rewriting files in Google Cloud Storage
Rewriting Files in GCP Note: even though this code is in Python, this should be the same idea in JavaScript, Go, etc. I wrote the following to copy a file from one Google Cloud Storage […]
Interpreting Spatial Data in the Age of COVID-19
As 2020 has come to an end, many are eager to leave the mess of COVID-19 behind with the new year and gain a fresh start. Unfortunately, new cases are still soaring across the United […]
Building a Better Mousetrap
Recently, my daughter (age 6) was into building “mousetraps” out of shoe boxes. These were more or less comfortable cardboard mouse houses complete with beds, rooms, everything a mouse could want or need and not […]
ARM Wrestling Its Way Into Mainstream Software Development
Nearly all smart phones have been running ARM-based processors for years. They provide superior power for the amount of power consumed, and thus extend battery life. With Apple’s recent release of the Apple Silicon M1 […]