Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Boot Preprocess JSON data

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?

like image 368
sisanared Avatar asked Mar 24 '17 13:03

sisanared


1 Answers

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);
            }

        };
    }
}
like image 143
Sébastien Temprado Avatar answered Nov 11 '22 09:11

Sébastien Temprado