可以将 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.Jackson2ObjectMapperFactoryBean">
<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