开发者

Can Jackson be configured to trim leading/trailing whitespace from all string properties?

Example JSON (note that the string has trailing spaces):

{ "aNumber": 0, "aString": "string   " }

Ideally, the deserialised instance would have an aString property with a value of "string" (i.e. without trailing spaces). This seems like something that is probably supported but I can't find it (e.g. in DeserializationConfig.Feature).

We're using Spring MVC 3.x so a Spring-based solution would also be fine.

I tried configuring Spring's WebDataBinder based on a suggestion in a forum post but it does not seem to work when using a Jackson message converter:

@InitBinder
public void initBinder( WebDataBinder binder )
{
    binder.registerCustomEditor( String.class, new StringT开发者_C百科rimmerEditor( " \t\r\n\f", true ) );
}


Easy solution for Spring Boot users, just add that walv's SimpleModule extension to your application context:

package com.example;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
public class StringTrimModule extends SimpleModule {

    public StringTrimModule() {
        addDeserializer(String.class, new StdScalarDeserializer<String>(String.class) {
            @Override
            public String deserialize(JsonParser jsonParser, DeserializationContext ctx) throws IOException,
                    JsonProcessingException {
                return jsonParser.getValueAsString().trim();
            }
        });
    }
}

Another way to customize Jackson is to add beans of type com.fasterxml.jackson.databind.Module to your context. They will be registered with every bean of type ObjectMapper, providing a global mechanism for contributing custom modules when you add new features to your application.

http://docs.spring.io/spring-boot/docs/current/reference/html/howto-spring-mvc.html#howto-customize-the-jackson-objectmapper

if you are not using spring boot, you have to register the StringTrimModule yourself (you do not need to annotate it with @Component)

<bean class="org.springframework.http.converter.json.Jackson2Objec‌​tMapperFactoryBean">
    <property name="modulesToInstall" value="com.example.StringTrimModule"/>
</bean


With a custom deserializer, you could do the following:

 <your bean>
 @JsonDeserialize(using=WhiteSpaceRemovalSerializer.class)
 public void setAString(String aString) {
    // body
 }

 <somewhere>
 public class WhiteSpaceRemovalDeserializer extends JsonDeserializer<String> {
     @Override
     public String deserialize(JsonParser jp, DeserializationContext ctxt) {
         // This is where you can deserialize your value the way you want.
         // Don't know if the following expression is correct, this is just an idea.
         return jp.getCurrentToken().asText().trim();
     }
 }

This solution does imply that this bean attribute will always be serialized this way, and you will have to annotate every attribute that you want to be deserialized this way.


I think it is better to extend default StringDeserializer as it already handles some specific cases (see here and here) that can be used by third party libraries. Below you can find configuration for Spring Boot. This is possible only with Jackson 2.9.0 and above as starting from 2.9.0 version StringDeserializer is not final anymore. If you have Jackson version below 2.9.0 you can still copy content of StringDeserializer to your code to handle above mentioned cases.

@JsonComponent
public class StringDeserializer extends com.fasterxml.jackson.databind.deser.std.StringDeserializer {

    @Override
    public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        String value = super.deserialize(p, ctxt);
        return value != null ? value.trim() : null;
    }
}


The problem of annotation @JsonDeserialize is that you must always remember to put it on the setter. To make it globally "once and forever" with Spring MVC, I did next steps:

pom.xml:

<dependency>
   <groupId>com.fasterxml.jackson.core</groupId>
   <artifactId>jackson-databind</artifactId>
   <version>2.3.3</version>
</dependency>

Create custom ObjectMapper:

package com.mycompany;

    import java.io.IOException;
    import org.apache.commons.lang3.StringUtils;
    import com.fasterxml.jackson.core.JsonParser;
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.DeserializationContext;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer;
    import com.fasterxml.jackson.databind.module.SimpleModule;

    public class MyObjectMapper extends ObjectMapper {

        public MyObjectMapper() {
            registerModule(new MyModule());
        }
    }

    class MyModule extends SimpleModule {

        public MyModule() {
            addDeserializer(String.class, new StdScalarDeserializer<String>  (String.class) {
                @Override
                public String deserialize(JsonParser jp, DeserializationContext  ctxt) throws IOException,
                    JsonProcessingException {
                    return StringUtils.trim(jp.getValueAsString());
                }
            });
        }
    }

Update Spring's servlet-context.xml:

<bean id="objectMapper" class="com.mycompany.MyObjectMapper" />

    <mvc:annotation-driven>
        <mvc:message-converters>
            <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
                <property name="objectMapper" ref="objectMapper" />
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>


For Spring Boot, we just have to create a custom deserializer as documented in the manual.

The following is my Groovy code but feel free to adapt it to work in Java.

import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonDeserializer
import org.springframework.boot.jackson.JsonComponent

import static com.fasterxml.jackson.core.JsonToken.VALUE_STRING

@JsonComponent
class TrimmingJsonDeserializer extends JsonDeserializer<String> {

    @Override
    String deserialize(JsonParser parser, DeserializationContext context) {
        parser.hasToken(VALUE_STRING) ? parser.text?.trim() : null
    }
}


com.fasterxml.jackson.dataformat

pom.xml

   <dependency>
      <groupId>com.fasterxml.jackson.dataformat</groupId>
      <artifactId>jackson-dataformat-csv</artifactId>
      <version>2.5.3</version>
    </dependency>

CsvUtil.java

     CsvSchema bootstrapSchema = CsvSchema.emptySchema().withHeader().sortedBy();
     CsvMapper mapper = new CsvMapper();
     mapper.enable(CsvParser.Feature.TRIM_SPACES);
     InputStream inputStream = ResourceUtils.getURL(fileName).openStream();
     MappingIterator<T> readValues =
          mapper.readerFor(type).with(bootstrapSchema).readValues(inputStream);


I propose you the following:

First, create a module to trim and put it into a class:

import java.io.IOException;

import org.springframework.stereotype.Component;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;

@Component
public class StringTrimModule extends SimpleModule {

    public StringTrimModule() {
        addDeserializer(String.class, new StdScalarDeserializer<String>(String.class) {
            @Override
            public String deserialize(JsonParser jsonParser, DeserializationContext ctx) throws IOException {
                return jsonParser.getValueAsString().trim();
            }
        });
    }
}

Then, create a class to configure jackson and add the module:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;

/**
 * Class used to configure Jackson
 */
@Configuration
public class JacksonConfiguration {

    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new StringTrimModule());
        return mapper;
    }
}

That's it.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜