Сериализация / десериализация java 8 java.time с помощью картографа JSON Джексона
Как использовать JSON JSON Mapper с Java 8 LocalDateTime?
org.codehaus.jackson.map.JsonMappingException: невозможно создать экземпляр значения типа [простой тип, класс java.time.LocalDateTime] из JSON String; нет однострокового конструктора / фабричного метода (через цепочку ссылок: MyDTO["field1"]->SubDTO["date"])
26 ответов
Там нет необходимости использовать пользовательские сериализаторы / десериализаторы здесь. Используйте модуль datetime для jackson-modules-java8:
Модуль типа данных, позволяющий Джексону распознавать типы данных API Java 8 Date & Time (JSR-310).
Этот модуль добавляет поддержку для нескольких классов:
- продолжительность
- Мгновенный
- LocalDateTime
- LocalDate
- Местное время
- День месяца
- OffsetDateTime
- OffsetTime
- период
- Год
- Год месяц
- ZonedDateTime
- ZoneId
- ZoneOffset
Если вы используете класс ObjectMapper из fastxml, по умолчанию ObjectMapper не понимает класс LocalDateTime, поэтому вам нужно добавить еще одну зависимость в ваш gradle/maven:
compile 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.7.3'
Теперь вам нужно зарегистрировать поддержку типа данных, предлагаемую этой библиотекой, в объекте objectmapper, это можно сделать следующим образом:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.findAndRegisterModules();
Теперь в вашем jsonString вы можете легко поместить поле java.LocalDateTime следующим образом:
{
"user_id": 1,
"score": 9,
"date_time": "2016-05-28T17:39:44.937"
}
Сделав все это, ваш JSON-файл для преобразования объектов Java будет работать нормально, вы можете прочитать файл следующим образом:
objectMapper.readValue(jsonString, new TypeReference<List<User>>() {
});
Обновление: оставив этот ответ по историческим причинам, но я не рекомендую его. Пожалуйста, смотрите принятый ответ выше.
Скажите Джексону, чтобы он сопоставлялся с использованием пользовательских классов сериализации:
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime ignoreUntil;
предоставить пользовательские классы:
public class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
@Override
public void serialize(LocalDateTime arg0, JsonGenerator arg1, SerializerProvider arg2) throws IOException, JsonProcessingException {
arg1.writeString(arg0.toString());
}
}
public class LocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
@Override
public LocalDateTime deserialize(JsonParser arg0, DeserializationContext arg1) throws IOException, JsonProcessingException {
return LocalDateTime.parse(arg0.getText());
}
}
случайный факт: если я вкладываю классы выше и не делаю их статичными, сообщение об ошибке выглядит странно:org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/json;charset=UTF-8' not supported
Эта зависимость maven решит вашу проблему:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.6.5</version>
</dependency>
Одна вещь, с которой я столкнулся, это то, что часовой пояс ZonedDateTime меняется на GMT во время десериализации. Оказалось, что по умолчанию Джексон заменяет его одним из контекста. Чтобы сохранить зону, нужно отключить эту "функцию"
Jackson2ObjectMapperBuilder.json()
.featuresToDisable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
Все , что вам нужно знать, находится в документации Джексона https://www.baeldung.com/jackson-serialize-dates
Ad.9 быстро решил проблему за меня.
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
У меня была похожая проблема при использовании Spring boot. В Spring boot 1.5.1.RELEASE все, что мне нужно было сделать, это добавить зависимость:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
В новой версии Jackson JSR, например,
registerModule(new JSR310Module())
устарел, теперь предлагается JavaTimeModule
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JacksonFactory {
private static ObjectMapper objectMapper = null;
public static ObjectMapper getObjectMapper() {
if (objectMapper == null) {
objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
}
return objectMapper;
}
}
Если вы не можете использовать jackson-modules-java8
по каким-либо причинам вы можете (де) сериализовать мгновенное поле при длительном использовании @JsonIgnore
а также @JsonGetter
& @JsonSetter
:
public class MyBean {
private Instant time = Instant.now();
@JsonIgnore
public Instant getTime() {
return this.time;
}
public void setTime(Instant time) {
this.time = time;
}
@JsonGetter
private long getEpochTime() {
return this.time.toEpochMilli();
}
@JsonSetter
private void setEpochTime(long time) {
this.time = Instant.ofEpochMilli(time);
}
}
Пример:
@Test
public void testJsonTime() throws Exception {
String json = new ObjectMapper().writeValueAsString(new MyBean());
System.out.println(json);
MyBean myBean = new ObjectMapper().readValue(json, MyBean.class);
System.out.println(myBean.getTime());
}
доходность
{"epochTime":1506432517242}
2017-09-26T13:28:37.242Z
Если вы используете Джерси, вам нужно добавить зависимость Maven (jackson-datatype-jsr310), как предложили другие, и зарегистрировать свой экземпляр объектного сопоставления следующим образом:
@Provider
public class JacksonObjectMapper implements ContextResolver<ObjectMapper> {
final ObjectMapper defaultObjectMapper;
public JacksonObjectMapper() {
defaultObjectMapper = createDefaultMapper();
}
@Override
public ObjectMapper getContext(Class<?> type) {
return defaultObjectMapper;
}
private static ObjectMapper createDefaultMapper() {
final ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
return mapper;
}
}
При регистрации Джексона в ваших ресурсах, вам нужно добавить этот маппер следующим образом:
final ResourceConfig rc = new ResourceConfig().packages("<your package>");
rc
.register(JacksonObjectMapper.class)
.register(JacksonJaxbJsonProvider.class);
Если вы используете сериализатор Jackson, вот способ использования модулей даты:
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import org.apache.kafka.common.serialization.Serializer;
public class JacksonSerializer<T> implements Serializer<T> {
private final ObjectMapper mapper = new ObjectMapper()
.registerModule(new ParameterNamesModule())
.registerModule(new Jdk8Module())
.registerModule(new JavaTimeModule());
@Override
public byte[] serialize(String s, T object) {
try {
return mapper.writeValueAsBytes(object);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
}
Если у вас возникла эта проблема из-за инструментов GraphQL Java Tools и вы пытаетесь маршалировать Java Instant
из строки даты вам необходимо настроить свой SchemaParser для использования ObjectMapper с определенными конфигурациями:
В вашем классе GraphQLSchemaBuilder вставьте ObjectMapper и добавьте следующие модули:
ObjectMapper objectMapper =
new ObjectMapper().registerModule(new JavaTimeModule())
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
и добавляем в опции:
final SchemaParserOptions options = SchemaParserOptions.newOptions()
.objectMapperProvider(fieldDefinition -> objectMapper)
.typeDefinitionFactory(new YourTypeDefinitionFactory())
.build();
См. https://github.com/graphql-java-kickstart/graphql-spring-boot/issues/32
Это всего лишь пример того, как использовать его в модульном тесте, который я взломал для устранения этой проблемы. Ключевые ингредиенты
- mapper.registerModule(новый JavaTimeModule ());
- maven зависимость jackson-datatype-jsr310
Код: import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import org.testng.Assert; import org.testng.annotations.Test;
import java.io.IOException;
import java.io.Serializable;
import java.time.Instant;
class Mumu implements Serializable {
private Instant from;
private String text;
Mumu(Instant from, String text) {
this.from = from;
this.text = text;
}
public Mumu() {
}
public Instant getFrom() {
return from;
}
public String getText() {
return text;
}
@Override
public String toString() {
return "Mumu{" +
"from=" + from +
", text='" + text + '\'' +
'}';
}
}
public class Scratch {
@Test
public void JacksonInstant() throws IOException {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
Mumu before = new Mumu(Instant.now(), "before");
String jsonInString = mapper.writeValueAsString(before);
System.out.println("-- BEFORE --");
System.out.println(before);
System.out.println(jsonInString);
Mumu after = mapper.readValue(jsonInString, Mumu.class);
System.out.println("-- AFTER --");
System.out.println(after);
Assert.assertEquals(after.toString(), before.toString());
}
}
Для тех, кто ищет решение на версии ES-8 и Spring Boot: 3.0
Создайте файл конфигурации, расширяющийElasticsearchConfiguration
и переопределить создание clientConfiguration и elasticsearchClient.
Во время создания elasticsearchClient внедрите свой собственный objectMapper, настроенный на использование модуля времени Java 8, который переопределит objectMapper по умолчанию.
@Override
public ClientConfiguration clientConfiguration() {
return ClientConfiguration.builder()
.connectedTo(<Hostname> +":"+ <Port>)
.usingSsl()
.withBasicAuth(<Username>, <Password>)
.build();
}
@Override
public ElasticsearchClient elasticsearchClient(RestClient restClient) {
Assert.notNull(restClient, "restClient must not be null");
//Create Java8 time module
JavaTimeModule module = new JavaTimeModule();
module.addSerializer(new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DateFormat.date_time_no_millis.getPattern())));
//Register the module with objectMapper
ObjectMapper objectMapper=new ObjectMapper()
.registerModule(module);
//To convert datetime to ISO-8601
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
//Creating our own jsonpMapper
JsonpMapper jsonpMapper=new JacksonJsonpMapper(objectMapper);
// Create the transport with a Jackson mapper
ElasticsearchTransport transport = new RestClientTransport(
restClient, jsonpMapper);
// And create the API client
return new ElasticsearchClient(transport);
}
Зависимость Мавена:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.14.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-parameter-names</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
добавьте эти зависимости и включите эти модули. это должно помочь
private static final ObjectMapper mapper = new ObjectMapper().findAndRegisterModules();
Для весенней загрузки api:
@Configuration
public class JsonConfig {
@Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new ParameterNamesModule())
.registerModule(new Jdk8Module())
.registerModule(new JavaTimeModule());
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);// will remove value properties
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
jsonConverter.setObjectMapper(mapper);
return jsonConverter;
}
}
импортировать следующие зависимости:
implementation 'com.fasterxml.jackson.core:jackson-core:2.13.0'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.0'
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.13.0'
Я использую этот формат времени: "{birthDate": "2018-05-24T13:56:13Z}"
десериализовать из json в java.time.Instant (см. скриншот)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
@JsonSerialize(using = LocalDateTimeSerializer.class)
private LocalDateTime createTime;
Это сработало для меня.
Вы можете установить это в своем application.yml
файл для разрешения Мгновенное время, которое является API даты в java8:
spring.jackson.serialization.write-dates-as-timestamps=false
Если вы используете загрузку Spring и имеете эту проблему с OffsetDateTime, тогда вам нужно использовать registerModules, как было сказано выше @greperror(отвечено 28 мая '16 в 13:04), но обратите внимание, что есть одно отличие. Упомянутая зависимость не нуждается в добавлении, так как я предполагаю, что у весенней загрузки она уже есть. У меня была эта проблема с загрузкой Spring, и она работала для меня без добавления этой зависимости.
Если у кого-то возникла проблема при использовании SpringBoot
вот как я исправил проблему без добавления новой зависимости.
В Spring 2.1.3
Джексон ожидает строку даты 2019-05-21T07:37:11.000
в этом yyyy-MM-dd HH:mm:ss.SSS
формат для десериализации в LocalDateTime
. Убедитесь, что строка даты разделяет дату и время с помощьюT
не с space
. секунды (ss
) и миллисекунды (SSS
) можно было опустить.
@JsonProperty("last_charge_date")
public LocalDateTime lastChargeDate;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm")
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime priceDateTime;
Для тех, кто использует Spring Boot 2.x
Нет необходимости делать что-либо из вышеперечисленного - Java 8 LocalDateTime сериализуется / десериализуется из коробки. Мне нужно было сделать все вышеперечисленное в 1.x, но с Boot 2.x все работает без проблем.
Смотрите также эту ссылку JSON Java 8 LocalDateTime формат в Spring Boot
ObjectMapper objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.registerModule(new JavaTimeModule());
Это сработало для меня
Я хотел оказать поддержку Spring DurationStyle
синтаксический анализ , поддерживаемый в файлах свойств в моих пользовательских файлах конфигурации, десериализованных с использованием Джексона, например сериализации
20s
на Продолжительность
PT20S
. Я сделал это, зарегистрировав собственный десериализатор на
ObjectMapper
экземпляр используется для того же:
@Bean("customConfigMapper")
public ObjectMapper customConfigMapper() {
final ObjectMapper mapper = new ObjectMapper();
final SimpleModule module = new SimpleModule();
module.addDeserializer(Duration.class, new SpringDurationStyleDeserializer());
mapper.registerModule(module);
return mapper;
}
public static class SpringDurationStyleDeserializer extends JsonDeserializer<Duration> {
@Override
public Duration deserialize(JsonParser jsonParser, DeserializationContext __) throws IOException {
return Optional.ofNullable(jsonParser.getText()).map(DurationStyle::detectAndParse).orElse(null);
}
}
К сожалению, предложенное здесь решение не сработало в моей среде. Но, честно говоря, использование объектов времени java8 в качестве DTO, в конце концов, не очень хорошая идея.
Я бы рекомендовал вместо этого создавать собственные DTO и не полагаться на нестабильные библиотеки, которые могут сломаться после следующего выпуска jdk. Такой подход также соответствует передовой практике антикоррупционного слоя и шаблонов адаптеров.
Вот пример DTO:
public class ReportDTO implements Serializable {
private YearMonthDTO yearMonth;
public YearMonthDTO getYearMonth() {
return yearMonth;
}
public void setYearMonth(final YearMonthDTO yearMonth) {
this.yearMonth = yearMonth;
}
public void fromYearMonth(final YearMonth yearMonth) {
this.yearMonth = new YearMonthDTO(yearMonth.getYear(),
yearMonth.getMonthValue());
}
}
public static class YearMonthDTO {
private int year;
private int monthValue;
public YearMonthDTO() {
}
public YearMonthDTO(int year, int monthValue) {
this.year = year;
this.monthValue = monthValue;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public int getMonthValue() {
return monthValue;
}
public void setMonthValue(int monthValue) {
this.monthValue = monthValue;
}
}
Конечно, это зависит от вашей ситуации и объема работы, которую вам придется проделать с этим решением. Как и любой шаблон, это решение применимо не ко всем ситуациям.
В любом случае, текущий лучший ответ, похоже, больше не работает. Других решений я не пробовал, но решил в моем простом случае не полагаться ни на какие библиотеки.
Если вы решили использовать fastjson, вы можете решить свою проблему, обратите внимание на версию
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.56</version>
</dependency>