Thursday, February 15, 2024

Custom SpringBoot Serializer for REST Controller - Fairly Elegant Way

SpringBoot is great for speed of development when you are familiar with it. Modern development often is centered on microservices which is heavily reliant upon a REST controller that returns JSON formatted data.

At some point there is a high chance that you'll need to customize the Serialization of data returned from the REST controller.

At first glance it seems straightforward enough, just define your custom serializer and reference it within in the @JsonSerializer annotation: @JsonSerialize(using = MyCustomSerializer.class)

If you do not register your custom Serializer you will likely get a 500 error stating that a serializer could not be found.

You must also make sure that you define a handledType() in your custom Serializer. If not you will see a message along the lines of:
... does not define valid handledType()

Furthermore you cannot use @JsonSerializer(using = ...) in your REST controller where your Serializer is defined, for that you need a custom annotation.

Most tutorials online I've found have circumvented these steps and provided kludgey solutions reliant on creation of another class - unregistered serializers are found when a wrapper class for instance is utilized defined within the REST controller.

Of course, you would probably put your custom Serializer somewhere outside of your REST controller, but for simplicity sake this example has it all in one.

import ... 
...
@RestController
@Component // Make sure our @Bean is found 
... 

////
// Define our Serializer with handler 
////
public class MyLongSerializer extends JsonSerializer<Long> {
    private static final long serialVersionUID = -8885817712043352432L;

    @Override
    public void serialize(Long value, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
            throws IOException {
    	jsonGenerator.writeStartObject();
        jsonGenerator.writeNumberField("my val", value);
        jsonGenerator.writeEndObject();
    }
    
    @Override
    public Class<Long> handledType() {
	return Long.class;
    }

}

////
// Define our custom Serializer annotation - @MyLong
////
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonInclude(value = Include.NON_NULL)
@JsonSerialize(using = MyLongSerializer.class)
public @interface MyLong {
}

////
// Define our REST controller test 
////
@RequestMapping(value = { "/testLong" }, method = RequestMethod.GET, produces = {
		MediaType.APPLICATION_JSON_VALUE }, consumes = MediaType.ALL_VALUE)
public Long testLong() { 
    @MyLong
    Long myLong = 293874928749284L;
	return myLong;
}

////
// Add our dear custom serializers
////
@Bean
public Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder() {
    SimpleModule m = new SimpleModule();
    m.addSerializer(new MyLongSerializer());
    // Add here as many as you have: 
     // m.addSerializer(...); ... 
    return new Jackson2ObjectMapperBuilder().modules(m);
}	

Hit your url: http://.../testLong and you should see:
{
"my val": 293874928749284
}