Custom StringDeserializer in a Spring Web MVC Project

I’m working on a Spring MVC project and I need to globally override the default StringDeserializer provided by Jackson to sanitize incoming JSON strings. My goal is to apply this sanitization to all String fields in my DTOs without having to annotate each fiels individually (more that 200 DTOs to cover).

I have created a custom StringDeserializer and added it in a Spring Configuration, but it is never called.

The custom deserializer:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>import org.jsoup.Jsoup;
import org.jsoup.safety.Safelist;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StringDeserializer;
public class SanitizeDeserializer extends StringDeserializer {
@Override
public String deserialize(
final JsonParser aParser,
final DeserializationContext aContext) throws IOException {
// this code is never executed
return Jsoup.clean(super.deserialize(aParser, aContext), Safelist.none());
}
}
</code>
<code>import org.jsoup.Jsoup; import org.jsoup.safety.Safelist; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.deser.std.StringDeserializer; public class SanitizeDeserializer extends StringDeserializer { @Override public String deserialize( final JsonParser aParser, final DeserializationContext aContext) throws IOException { // this code is never executed return Jsoup.clean(super.deserialize(aParser, aContext), Safelist.none()); } } </code>
import org.jsoup.Jsoup;
import org.jsoup.safety.Safelist;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StringDeserializer;

public class SanitizeDeserializer extends StringDeserializer {

  @Override
  public String deserialize(
      final JsonParser aParser,
      final DeserializationContext aContext) throws IOException {
    // this code is never executed
    return Jsoup.clean(super.deserialize(aParser, aContext), Safelist.none());
  }    
}

The Spring configuration to register the Deserializer:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
@Configuration
public class SanitizeConfiguration {
@Bean
public Module addSanitizeStringDeserializer() {
// this code is well executed when running the app
final SimpleModule module = new SimpleModule();
module.addDeserializer(String.class, new SanitizeDeserializer());
return module;
}
}
</code>
<code>import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; @Configuration public class SanitizeConfiguration { @Bean public Module addSanitizeStringDeserializer() { // this code is well executed when running the app final SimpleModule module = new SimpleModule(); module.addDeserializer(String.class, new SanitizeDeserializer()); return module; } } </code>
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;

@Configuration
public class SanitizeConfiguration {
    
  @Bean
  public Module addSanitizeStringDeserializer() {
    // this code is well executed when running the app
    final SimpleModule module = new SimpleModule();
    module.addDeserializer(String.class, new SanitizeDeserializer());
    return module;
  }
}

REST controller sample:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code> @PutMapping("/document/{id}")
public ResponseEntity<String> save(@RequestBody DocumentDTO document) {
return ResponseEntity.ok(document.getId());
}
@Getters
@Setters
public class DocumentDTO {
// I want these String fields to be sanitize before the setter is called when deserializing the JSON
private String id;
private String title;
private String content;
}
</code>
<code> @PutMapping("/document/{id}") public ResponseEntity<String> save(@RequestBody DocumentDTO document) { return ResponseEntity.ok(document.getId()); } @Getters @Setters public class DocumentDTO { // I want these String fields to be sanitize before the setter is called when deserializing the JSON private String id; private String title; private String content; } </code>
 @PutMapping("/document/{id}")
    public ResponseEntity<String> save(@RequestBody DocumentDTO document) {
        return ResponseEntity.ok(document.getId());
    }

@Getters
@Setters
public class DocumentDTO {
  // I want these String fields to be sanitize before the setter is called when deserializing the JSON
  private String id;
  private String title;
  private String content;
}

I put a breakpoint in the deserialize method, but it is never called, it breaks in the Jackson StringDeserializer method.

Am I doing something wrong, or am I missing a configuration?

Or maybe it is not feasible to customize serializer on an already defined type?

Edit 1:

I tried configuring MessageConverters, but it override the default conf (WebMvcConfigurationSupport.addDefaultHttpMessageConverters), and while all String fields are well deserialized by custom deserializer, the XML content conversion is not working as before.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code> @Override
public void configureMessageConverters(
final List<HttpMessageConverter<?>> aConverters) {
final Jackson2ObjectMapperBuilder iBuilder = new Jackson2ObjectMapperBuilder().deserializerByType(String.class,
new SanitizeDeserializer());
aConverters.add(new MappingJackson2HttpMessageConverter(iBuilder.build()));
// if i remove the following line, the XML response are not handle (500 error)
// but with this line, XML reponse are XML escaped and become invalid
aConverters.add(new MappingJackson2XmlHttpMessageConverter(new Jackson2ObjectMapperBuilder().xml().build()));
}
</code>
<code> @Override public void configureMessageConverters( final List<HttpMessageConverter<?>> aConverters) { final Jackson2ObjectMapperBuilder iBuilder = new Jackson2ObjectMapperBuilder().deserializerByType(String.class, new SanitizeDeserializer()); aConverters.add(new MappingJackson2HttpMessageConverter(iBuilder.build())); // if i remove the following line, the XML response are not handle (500 error) // but with this line, XML reponse are XML escaped and become invalid aConverters.add(new MappingJackson2XmlHttpMessageConverter(new Jackson2ObjectMapperBuilder().xml().build())); } </code>
  @Override
  public void configureMessageConverters(
      final List<HttpMessageConverter<?>> aConverters) {
    final Jackson2ObjectMapperBuilder iBuilder = new Jackson2ObjectMapperBuilder().deserializerByType(String.class,
        new SanitizeDeserializer());
    aConverters.add(new MappingJackson2HttpMessageConverter(iBuilder.build()));
    // if i remove the following line, the XML response are not handle (500 error)
    // but with this line, XML reponse are XML escaped and become invalid
    aConverters.add(new MappingJackson2XmlHttpMessageConverter(new Jackson2ObjectMapperBuilder().xml().build()));
  }

Then I tried what suggested in comment, to add a Jackson2ObjectMapperBuilder Bean, but it is still not used:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code> @Bean
public Jackson2ObjectMapperBuilder configureJackson2ObjectMapperBuilder() {
final Jackson2ObjectMapperBuilder iBuilder = new Jackson2ObjectMapperBuilder();
final SimpleModule iSanitizeModule = new SimpleModule("sanitize-module");
iSanitizeModule.addDeserializer(String.class, new SanitizeDeserializer());
iBuilder.modules(iSanitizeModule);
return iBuilder;
}
@Bean
public MappingJackson2HttpMessageConverter configureMappingJackson2HttpMessageConverter() {
final MappingJackson2HttpMessageConverter iConverter = new MappingJackson2HttpMessageConverter();
iConverter.setObjectMapper(configureJackson2ObjectMapperBuilder().build());
return iConverter;
}
</code>
<code> @Bean public Jackson2ObjectMapperBuilder configureJackson2ObjectMapperBuilder() { final Jackson2ObjectMapperBuilder iBuilder = new Jackson2ObjectMapperBuilder(); final SimpleModule iSanitizeModule = new SimpleModule("sanitize-module"); iSanitizeModule.addDeserializer(String.class, new SanitizeDeserializer()); iBuilder.modules(iSanitizeModule); return iBuilder; } @Bean public MappingJackson2HttpMessageConverter configureMappingJackson2HttpMessageConverter() { final MappingJackson2HttpMessageConverter iConverter = new MappingJackson2HttpMessageConverter(); iConverter.setObjectMapper(configureJackson2ObjectMapperBuilder().build()); return iConverter; } </code>
  @Bean
  public Jackson2ObjectMapperBuilder configureJackson2ObjectMapperBuilder() {
    final Jackson2ObjectMapperBuilder iBuilder = new Jackson2ObjectMapperBuilder();
    final SimpleModule iSanitizeModule = new SimpleModule("sanitize-module");
    iSanitizeModule.addDeserializer(String.class, new SanitizeDeserializer());
    iBuilder.modules(iSanitizeModule);
    return iBuilder;
  }

  @Bean
  public MappingJackson2HttpMessageConverter configureMappingJackson2HttpMessageConverter() {
    final MappingJackson2HttpMessageConverter iConverter = new MappingJackson2HttpMessageConverter();
    iConverter.setObjectMapper(configureJackson2ObjectMapperBuilder().build());
    return iConverter;
  }

12

Here is the working solution, that keeps default Spring MessageConverters, and add a customized deserializer in the Jackson JSON converter.

The code for the custom Deserializer

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>public class SanitizeDeserializer extends StringDeserializer {
@Override
public String deserialize(
final JsonParser aParser,
final DeserializationContext aContext) throws IOException {
return Jsoup.clean(super.deserialize(aParser, aContext), Safelist.none());
}
}
</code>
<code>public class SanitizeDeserializer extends StringDeserializer { @Override public String deserialize( final JsonParser aParser, final DeserializationContext aContext) throws IOException { return Jsoup.clean(super.deserialize(aParser, aContext), Safelist.none()); } } </code>
public class SanitizeDeserializer extends StringDeserializer {

  @Override
  public String deserialize(
      final JsonParser aParser,
      final DeserializationContext aContext) throws IOException {
    return Jsoup.clean(super.deserialize(aParser, aContext), Safelist.none());
  }    
}

And then, in @EnableWebMvc class extending WebMvcConfigurer, override the extendMessageConverters to replace the default Jackson JSON MessageConverter by your custom one:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<code>@Override
public void extendMessageConverters(
final List<HttpMessageConverter<?>> aConverters) {
aConverters.removeIf(MappingJackson2HttpMessageConverter.class::isInstance);
final Jackson2ObjectMapperBuilder iBuilder = new Jackson2ObjectMapperBuilder().deserializerByType(String.class,
new SanitizeDeserializer());
aConverters.add(new MappingJackson2HttpMessageConverter(iBuilder.build()));
}
</code>
<code>@Override public void extendMessageConverters( final List<HttpMessageConverter<?>> aConverters) { aConverters.removeIf(MappingJackson2HttpMessageConverter.class::isInstance); final Jackson2ObjectMapperBuilder iBuilder = new Jackson2ObjectMapperBuilder().deserializerByType(String.class, new SanitizeDeserializer()); aConverters.add(new MappingJackson2HttpMessageConverter(iBuilder.build())); } </code>
@Override
public void extendMessageConverters(
    final List<HttpMessageConverter<?>> aConverters) {
  aConverters.removeIf(MappingJackson2HttpMessageConverter.class::isInstance);
  final Jackson2ObjectMapperBuilder iBuilder = new Jackson2ObjectMapperBuilder().deserializerByType(String.class,
      new SanitizeDeserializer());
  aConverters.add(new MappingJackson2HttpMessageConverter(iBuilder.build()));
}

See Spring documentation https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-config/message-converters.html#page-title

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật