serialize/deserialize java 8 java.time with Jackson JSON mapper
JacksonJava TimeJackson Problem Overview
How do I use Jackson JSON mapper with Java 8 LocalDateTime?
>org.codehaus.jackson.map.JsonMappingException: Can not instantiate value of type [simple type, class java.time.LocalDateTime] from JSON String; no single-String constructor/factory method (through reference chain: MyDTO["field1"]->SubDTO["date"])
Jackson Solutions
Solution 1 - Jackson
There's no need to use custom serializers/deserializers here. Use jackson-modules-java8's datetime module:
> Datatype module to make Jackson recognize Java 8 Date & Time API data types (JSR-310).
This module adds support for quite a few classes:
- Duration
- Instant
- LocalDateTime
- LocalDate
- LocalTime
- MonthDay
- OffsetDateTime
- OffsetTime
- Period
- Year
- YearMonth
- ZonedDateTime
- ZoneId
- ZoneOffset
Solution 2 - Jackson
Update: Leaving this answer for historical reasons, but I don't recommend it. Please see the accepted answer above.
Tell Jackson to map using your custom [de]serialization classes:
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime ignoreUntil;
provide custom classes:
public class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
@Override
public void serialize(LocalDateTime arg0, JsonGenerator arg1, SerializerProvider arg2) throws IOException {
arg1.writeString(arg0.toString());
}
}
public class LocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
@Override
public LocalDateTime deserialize(JsonParser arg0, DeserializationContext arg1) throws IOException {
return LocalDateTime.parse(arg0.getText());
}
}
random fact: if i nest above classes and don't make them static, the error message is weird:
org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/json;charset=UTF-8' not supported
Solution 3 - Jackson
If you are using ObjectMapper class of fasterxml, by default ObjectMapper do not understand the LocalDateTime class, so, you need to add another dependency in your gradle/maven :
compile 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.7.3'
Now you need to register the datatype support offered by this library into you objectmapper object, this can be done by following :
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.findAndRegisterModules();
Now, in your jsonString, you can easily put your java.LocalDateTime field as follows :
{
"user_id": 1,
"score": 9,
"date_time": "2016-05-28T17:39:44.937"
}
By doing all this, your Json file to Java object conversion will work fine, you can read the file by following :
objectMapper.readValue(jsonString, new TypeReference<List<User>>() {
});
Solution 4 - Jackson
This maven dependency will solve your problem:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.6.5</version>
</dependency>
One thing I've struggled is that for ZonedDateTime timezone being changed to GMT during deserialization. Turned out, that by default jackson replaces it with one from context.. To keep zone one must disable this 'feature'
Jackson2ObjectMapperBuilder.json()
.featuresToDisable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
Solution 5 - Jackson
I had a similar problem while using Spring boot. With Spring boot 1.5.1.RELEASE all I had to do is to add dependency:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
Solution 6 - Jackson
all you need to know is in Jackson Documentation https://www.baeldung.com/jackson-serialize-dates
Ad.9 quick solved the problem for me.
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
Solution 7 - Jackson
If you are using Jersey then you need to add the Maven dependency (jackson-datatype-jsr310) as the others suggested and register your object mapper instance like so:
@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;
}
}
When registering Jackson in your resources, you need to add this mapper like so:
final ResourceConfig rc = new ResourceConfig().packages("<your package>");
rc
.register(JacksonObjectMapper.class)
.register(JacksonJaxbJsonProvider.class);
Solution 8 - Jackson
If you can't use jackson-modules-java8
for whatever reasons you can (de-)serialize the instant field as long
using @JsonIgnore
and @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);
}
}
Example:
@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());
}
yields
{"epochTime":1506432517242}
2017-09-26T13:28:37.242Z
Solution 9 - Jackson
If you are using Jackson Serializer, here is a way to use the date modules:
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;
}
}
Solution 10 - Jackson
In the newer version of Jackson JSR, e.g., the registerModule(new JSR310Module())
is deprecated, now the suggested one is 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;
}
}
Solution 11 - Jackson
This is just an example how to use it in a unit test that I hacked to debug this issue. The key ingredients are
mapper.registerModule(new JavaTimeModule());
- maven dependency of
<artifactId>jackson-datatype-jsr310</artifactId>
Code:
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());
}
}
Solution 12 - Jackson
If you're having this issue because of GraphQL Java Tools and trying to marshal an Java Instant
from a date string, you need to setup your SchemaParser to use an ObjectMapper with certain configurations:
In your GraphQLSchemaBuilder class, inject ObjectMapper and add this modules:
ObjectMapper objectMapper =
new ObjectMapper().registerModule(new JavaTimeModule())
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
and add it to the options:
final SchemaParserOptions options = SchemaParserOptions.newOptions()
.objectMapperProvider(fieldDefinition -> objectMapper)
.typeDefinitionFactory(new YourTypeDefinitionFactory())
.build();
See https://github.com/graphql-java-kickstart/graphql-spring-boot/issues/32
Solution 13 - Jackson
<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>
add these dependencies and enable these modules. that should help
private static final ObjectMapper mapper = new ObjectMapper().findAndRegisterModules();
Solution 14 - Jackson
I use this time format: "{birthDate": "2018-05-24T13:56:13Z}"
to deserialize from json into java.time.Instant (see screenshot)
Solution 15 - Jackson
You may set this in your application.yml
file to resolve Instant time, which is Date API in java8:
spring.jackson.serialization.write-dates-as-timestamps=false
Solution 16 - Jackson
If you are using Spring boot and have this issue with the OffsetDateTime then need to use the registerModules as answered above by @greperror(answered May 28 '16 at 13:04) but note that there is one difference. The dependency mentioned doesn't need to be added as I am guessing that spring boot has it already. I was having this issue with Spring boot and it worked for me without adding this dependency.
Solution 17 - Jackson
For those who use Spring Boot 2.x
There is no need to do any of the above - Java 8 LocalDateTime is serialised/de-serialised out of the box. I had to do all of the above in 1.x, but with Boot 2.x, it works seamlessly.
See this reference too https://stackoverflow.com/q/29956175/6043669
Solution 18 - Jackson
If any one having problem while using SpringBoot
here is how I fixed the issue without adding new dependency.
In Spring 2.1.3
Jackson expects date string 2019-05-21T07:37:11.000
in this yyyy-MM-dd HH:mm:ss.SSS
format to de-serialize in LocalDateTime
. Make sure date string separates the date and time with T
not with space
. seconds (ss
) and milliseconds(SSS
) could be ommitted.
@JsonProperty("last_charge_date")
public LocalDateTime lastChargeDate;
Solution 19 - Jackson
I wanted to provide support for Spring's DurationStyle
parsing, supported in property files in my custom configuration files deserialized using Jackson, like serializing 20s
to Duration PT20S
. I did this by registering a custom deserializer on the ObjectMapper
instance being used for the same:
@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);
}
}
Solution 20 - Jackson
Unfortunately, the solution proposed here, didn't work in my environment. But to be honest, using java8 time objects as DTOs is not a very good idea after all.
I would recommend to create custom DTOs instead, and don't rely on the unstable libraries, which might break after next jdk release. This approach is also in accordance with good practices of anticorruption layer and adapter patterns.
Here is the example of the 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;
}
}
It of course depends on your situation, and the amount of work you would have to do with this solution. As any pattern, this solution is not applicable to all situations.
In any case, the current best answer doesn't seem to work anymore. I didn't try other solutions, but I decided not to rely on any libraries in my simple case.
Solution 21 - Jackson
ObjectMapper objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.registerModule(new JavaTimeModule());
This worked for me
Solution 22 - Jackson
For spring boot 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;
}
}
import the following dependencies :
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'
Solution 23 - Jackson
If you consider using fastjson, you can solve your problem, note the version
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.56</version>
</dependency>