可以将 Jackson 配置为从所有字符串属性中修剪前导/尾随空格吗?

Posted

技术标签:

【中文标题】可以将 Jackson 配置为从所有字符串属性中修剪前导/尾随空格吗?【英文标题】:Can Jackson be configured to trim leading/trailing whitespace from all string properties? 【发布时间】:2011-10-14 16:44:54 【问题描述】:

示例 JSON(注意字符串有尾随空格):

 "aNumber": 0, "aString": "string   " 

理想情况下,反序列化的实例应该有一个 aString 属性,其值为 "string"(即没有尾随空格)。这似乎是可能受支持的东西,但我找不到它(例如在 DeserializationConfig.Feature 中)。

我们使用的是 Spring MVC 3.x,因此基于 Spring 的解决方案也可以。

我尝试根据forum post 中的建议配置 Spring 的 WebDataBinder,但在使用 Jackson 消息转换器时它似乎不起作用:

@InitBinder
public void initBinder( WebDataBinder binder )

    binder.registerCustomEditor( String.class, new StringTrimmerEditor( " \t\r\n\f", true ) );

【问题讨论】:

您是否 100% 确定空格不是实际值?因为我从未见过杰克逊这样做。或者您是说您传递给 Jackson 的课程故意有这些尾随空格,并且您想设置 Jackson 为您删除它? @matt:我认为已经很清楚地表明数据有来自源的尾随空格,他想配置 Jackson 以在反序列化时删除尾随空格。 没错,我们没有正当理由在传入的 JSON 消息中保留尾随(或前导)空格。 【参考方案1】:

使用custom deserializer,您可以执行以下操作:

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

此解决方案确实暗示此 bean 属性将始终以这种方式序列化,并且您必须注释要以这种方式反序列化的每个属性。

【讨论】:

谢谢,虽然我认为 this.aString = aString.trim() 可能更简单 :-) 希望它会成为未来版本的一个功能。 @DCKing:为什么不通过模块接口全局注册您的自定义反序列化器?当 Jackson 仅用于 RESTful Web 服务时,我想不出在典型的 Spring 应用程序中有什么不好的后果,你可以吗? @ArtemShafranov 请注意,如果使用 afterburner,则注册全局反序列化器将不起作用,因为 afterburner 会优化那些默认反序列化器并且不允许自定义反序列化器,除非使用 JsonDeserialize 注释。 几件事情; asText() 给了我一个“找不到符号”的错误。其他一切都解决了 jackson.core,而不是那种方法。此外,您最初为“自定义反序列化器”发布的链接已损坏。 @AlwaysLearning 你现在可以做jp.getText()【参考方案2】:

注解@JsonDeserialize 的问题是你必须时刻记住把它放在setter上。 为了使用 Spring MVC 使其在全球范围内“一劳永逸”,我做了以下步骤:

pom.xml:

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

创建自定义 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());
                
            );
        
    

更新 Spring 的 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>

【讨论】:

这似乎是一个不错的全局解决方案,很容易添加我已经使用自定义 ObjectMapper 的 Spring Boot 应用程序:我刚刚定义并注册了“StringTrimmerModule”。 (虽然我更喜欢普通的旧 String.trim() 用于非空值而不是 Commons StringUtils。) 请注意,如果使用afterburner,那么这将不起作用,因为加力优化了那些默认的反序列化器。如果是这样的话,那么我猜你被@JsonDeserialize 困住了? 此解决方案对非字符串的 JSON 属性(如数字或布尔值)有何影响 @WandMaker,其他类型将被忽略,因为此反序列化仅针对 String.class 被覆盖。【参考方案3】:

Spring Boot 用户的简单解决方案,只需将 walv 的 SimpleModule 扩展添加到您的应用程序上下文中:

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

另一种自定义 Jackson 的方法是将 com.fasterxml.jackson.databind.Module 类型的 bean 添加到您的上下文中。它们将在每个 ObjectMapper 类型的 bean 中注册,为您在应用程序中添加新功能时提供自定义模块提供全局机制。

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

如果你使用spring boot,你必须自己注册StringTrimModule(你不需要用@Component注解它)

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

【讨论】:

我们如何才能跳过某些特定字段的修剪过程? (例如密码字段) 标准的字符串反序列化器不仅仅是调用 jsonParser.getValueAsString()。另外,您不需要创建一个模块并使用@Component 注册它,您只需编写一个反序列化器并将@JsonComponent 添加到它:@JsonComponent class TrimStringDeserializer extends StringDeserializer @Override String deserialize(JsonParser jsonParser, DeserializationContext ctx) throws IOException String text = super.deserialize(jsonParser, ctx) return text != null ? text.trim() : text 如果我也想添加修剪字符串进行序列化,如何使用addSerializer() 添加它?【参考方案4】:

对于 Spring Boot,我们只需要创建一个自定义反序列化器为 documented in the manual。

以下是我的 Groovy 代码,您可以随意调整它以在 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
    

【讨论】:

我的观察是杰克逊对所有字符串都使用了这个反序列化器。如果请求类包含布尔值等字符串,则使用 parser.hasToken(VALUE_STRING) 将产生错误。如果您的 json 请求中有 boolean、uuid、int 等,您将从 JACKSON 获得空值。我的解决方案是简单地使用 parser.getText.trim() 它在我的机器上运行良好:)【参考方案5】:

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

【讨论】:

如果要修剪所有Strings也可以,但是使用自定义JsonDeserializer更灵活。【参考方案6】:

我认为最好扩展默认的 StringDeserializer,因为它已经处理了第三方库可以使用的一些特定情况(请参阅 here 和 here)。您可以在下面找到 Spring Boot 的配置。这仅适用于 Jackson 2.9.0 及更高版本,因为从 2.9.0 版本开始,StringDeserializer 不再是最终版本。如果您的 Jackson 版本低于 2.9.0,您仍然可以将 StringDeserializer 的内容复制到您的代码中以处理上述情况。

@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;
    

【讨论】:

顺便说一句,与 java.lang.String#trim 相比,StringUtils#trimWhitespace 的基准测试速度较慢。在这种情况下,java.lang.String#trim 的速度快了约 10 倍。

以上是关于可以将 Jackson 配置为从所有字符串属性中修剪前导/尾随空格吗?的主要内容,如果未能解决你的问题,请参考以下文章

使用 Jackson 将 JSON 字符串递归构建到 jsTree

Jackson工具使用

使用 Jackson 处理未知的 JSON 属性

热加载 MyBatis 中修改过的 Mapper.xml

jackson使用了@JsonIgnoreType的类被继承了之后子类会被忽略,怎么让子类不被忽略?

jackson 常用注解,比如忽略某些属性,驼峰和下划线互转