#java #spring-boot
Вопрос:
Пример JSON (обратите внимание, что строка содержит конечные пробелы):
{ "aNumber": 0, "aString": "string " }
В идеале десериализованный экземпляр должен иметь свойство aString со значением «строка» (т. е. Без конечных пробелов). Это похоже на то, что, вероятно, поддерживается, но я не могу его найти (например, в DeserializationConfig.Особенность).
Мы используем Spring MVC 3.x, поэтому решение на основе пружины также подойдет.
Я попытался настроить WebDataBinder Spring на основе предложения в сообщении на форуме, но, похоже, он не работает при использовании конвертера сообщений Джексона:
@InitBinder
public void initBinder( WebDataBinder binder )
{
binder.registerCustomEditor( String.class, new StringTrimmerEditor( " trnf", true ) );
}
Комментарии:
1. Вы на 100% уверены, что пробелы не соответствуют фактическому значению? Потому что я никогда не видел, чтобы Джексон делал это. Или вы хотите сказать, что класс, который вы передаете Джексону, намеренно содержит эти конечные пробелы, и вы хотите настроить Джексона, чтобы он удалил их для вас?
2. @мэтт: Я думал, что было довольно четко указано, что данные содержат конечные пробелы из источника, и он хочет настроить Джексона на удаление конечных пробелов при десериализации.
3. Это верно, у нас нет веских причин для того, чтобы оставлять пробелы в конце (или начале) во входящем сообщении JSON.
Ответ №1:
Простое решение для пользователей Spring Boot, просто добавьте расширение SimpleModule walv в контекст вашего приложения:
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();
}
});
}
}
Другой способ настройки Джексона-добавить компоненты типа com.fasterxml.jackson.databind.Модуль в соответствии с вашим контекстом. Они будут зарегистрированы в каждом компоненте типа ObjectMapper, обеспечивая глобальный механизм для добавления пользовательских модулей при добавлении новых функций в приложение.
если вы не используете spring boot, вам необходимо самостоятельно зарегистрировать модуль StringTrimModule (вам не нужно аннотировать его с помощью @Component).
<bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
<property name="modulesToInstall" value="com.example.StringTrimModule"/>
</bean
Комментарии:
1. Как мы можем пропустить этот процесс обрезки для некоторых конкретных полей? (например, поле пароля)
2. Стандартный десериализатор строк делает гораздо больше, чем просто вызов 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 } }
3. Если я также хочу добавить строку обрезки для сериализации, как я могу добавить ее с помощью
addSerializer()
?
Ответ №2:
С помощью пользовательского десериализатора вы можете выполнить следующее:
<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();
}
}
Это решение подразумевает, что этот атрибут компонента всегда будет сериализован таким образом, и вам придется аннотировать каждый атрибут, который вы хотите десериализовать таким образом.
Комментарии:
1. Спасибо, хотя я так думаю . aString = aString.trim () , вероятно, проще 🙂 Надеюсь, это будет функция в будущей версии.
2. @DCKing: Почему бы просто не зарегистрировать свой пользовательский десериализатор глобально через интерфейс модуля? Я не могу думать о каких-либо плохих последствиях в типичном весеннем приложении, когда Джексон используется только для веб-сервисов RESTful, не так ли?
3. @ArtemShafranov Обратите внимание, что если используется форсаж, то регистрация глобального десериализатора не будет работать, так как форсаж оптимизирует эти десериализаторы по умолчанию и не разрешает пользовательские, если они не аннотированы JsonDeserialize.
4. Пара вещей;
asText()
выдает мне ошибку «не удается найти символ». Все остальное решило джексон.ядро, просто не этот метод. Кроме того, ссылка, которую вы первоначально разместили для «пользовательского десериализатора», нарушена.5. @AlwaysLearning Теперь вы можете просто делать
jp.getText()
.
Ответ №3:
Проблема аннотации @JsonDeserialize заключается в том, что вы всегда должны помнить о том, чтобы поместить ее в сеттер. Чтобы сделать это глобально «раз и навсегда» с помощью Spring MVC, я сделал следующие шаги:
pom.xml:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.3.3</version>
</dependency>
Создайте пользовательский объект-сопоставитель:
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());
}
});
}
}
Обновите весенние 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>
Комментарии:
1. Это кажется хорошим глобальным решением, которое было легко добавить в мое приложение Spring Boot, уже использующее пользовательский объект: я только что определил и зарегистрировал «StringTrimmerModule» в соответствии с этими строками. (Хотя я предпочитал простые старые
String.trim()
значения для ненулевых значений вместо общих строк.)2. Обратите внимание, что если используется форсаж , то это не сработает, так как форсаж оптимизирует эти десериализаторы по умолчанию. Если это так, то, я думаю, вы застряли с @JsonDeserialize?
3. Каково влияние этого решения на атрибуты JSON, которые не являются строковыми числами или логическими значениями
4. @WandMaker, другие типы будут проигнорированы, потому что эта десериализация перезаписывается для String.class только.
Ответ №4:
Я думаю, что лучше расширить StringDeserializer по умолчанию, поскольку он уже обрабатывает некоторые конкретные случаи (см. Здесь и здесь), которые могут использоваться сторонними библиотеками. Ниже вы можете найти конфигурацию для весенней загрузки. Это возможно только с Jackson 2.9.0 и выше, так как начиная с версии 2.9.0 StringDeserializer больше не является окончательным. Если у вас версия Джексона ниже 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;
}
}
Комментарии:
1. Кстати, StringUtils#trimWhitespace показал медленные тесты по сравнению с java.lang. Строка#обрезка. Язык java.lang. Строка#обрезка в этом случае была примерно в 10 раз быстрее.
Ответ №5:
Для весенней загрузки нам просто нужно создать пользовательский десериализатор, как описано в руководстве.
Ниже приведен мой классный код, но не стесняйтесь адаптировать его для работы на 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
}
}
Комментарии:
1. Мое наблюдение состоит в том, что Джексон использует этот десериализатор для всех строк. Если класс запроса содержит строки для логического и т. Д., То наличие синтаксического анализатора.hasToken(VALUE_STRING) создаст ошибки. Если в вашем запросе json есть логическое значение, uuid, int и т. Д., Вы получите нулевые значения от ДЖЕКСОНА. Моим решением было просто использовать parser.getText.trim (), и он отлично работает на моей машине 🙂
Ответ №6:
com.fasterxml.jackson.формат данных
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);
Комментарии:
1. Это нормально, если вы хотите обрезать все строки, но использование пользовательского JsonDeserializer более гибко.