Issue
I have a RESTful Spring Boot API, that has a registration end point.
Inside my @RestController
class I have written a simple String value pre-processor, to trim and replace whitespace only strings with null
values.
@InitBinder
public void blankStringBinder(WebDataBinder dataBinder) {
dataBinder.setDisallowedFields("password", "confirmPassword");
StringTrimmerEditor stringTrimmerEditor = new StringTrimmerEditor(true);
dataBinder.registerCustomEditor(String.class, stringTrimmerEditor);
}
But when I submit the data from Postman as raw JSON
, the trimming edit is not taking place. I put a break point inside blankStringBinder
method and I see that it gets called on each incoming request.
The WebDataBinder
seems to work for form-data
. Is there a way to make it work for raw JSON
data too?
Solution
If you want to do this only on few String
fields, first create a custom JsonDeserializer
:
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
public class EmptyToNullCustomDeserializer extends JsonDeserializer<String> {
@Override
public String deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException {
JsonNode node = jp.readValueAsTree();
if (node.textValue().isEmpty()) {
return null;
}
return node.textValue();
}
}
Then add this annotation on each String
of the POJO: @JsonDeserialize(using = EmptyToNullCustomDeserializer.class)
.
For example :
@JsonDeserialize(using = EmptyToNullCustomDeserializer.class)
private String content;
Edit:
If you have got a lot of String
fields you want to pre-process, annotating each field can be really cumbersome.
As an alternative you can have all String
fields pre-processed, without having to annotate them at all. For this you have to first modify EmptyToNullCustomDeserializer
so that its parent class is Jacksons StdDeserializer
:
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import java.io.IOException;
import org.springframework.stereotype.Component;
@Component
public class EmptyToNullCustomDeserializer extends StdDeserializer<String> {
protected EmptyToNullCustomDeserializer() {
super(String.class);
}
@Override
public String deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException {
JsonNode node = jp.readValueAsTree();
if (node.textValue().isEmpty()) {
return null;
}
return node.textValue();
}
}
And then create this component in order to customize the Jackson object mappers by adding the above deserializer to the configuration:
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.NonNull;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.stereotype.Component;
@Component
public class JacksonConfiguration {
@Autowired
private EmptyToNullCustomDeserializer emptyToNullCustomDeserializer;
@Primary
@Bean
public Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder(List<Jackson2ObjectMapperBuilderCustomizer> customizers) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
for (Jackson2ObjectMapperBuilderCustomizer customizer : customizers) {
customizer.customize(builder);
}
return builder;
}
@Bean
public Jackson2ObjectMapperBuilderCustomizer addEmptyToNullStringDeserialization() {
return new Jackson2ObjectMapperBuilderCustomizer() {
@Override
public void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) {
jacksonObjectMapperBuilder.deserializerByType(String.class, emptyToNullCustomDeserializer);
}
};
}
}
Answered By - Sébastien Temprado