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"])
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
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
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>>() {
});
This maven dependency will solve your problem:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.13.4</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)
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);
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>
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
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;
}
}
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);
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;
}
}
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());
}
}
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
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'
<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();
I use this time format: "{birthDate": "2018-05-24T13:56:13Z}" to deserialize from json into java.time.Instant (see screenshot)
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
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.
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;
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);
}
}
ObjectMapper objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.registerModule(new JavaTimeModule());
This worked for me
#JsonDeserialize(using = LocalDateTimeDeserializer.class)
#JsonSerialize(using = LocalDateTimeSerializer.class)
private LocalDateTime createTime;
This has worked for me.
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 JSON Java 8 LocalDateTime format in Spring Boot
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.
For those who are looking for a solution on version ES-8 and Spring Boot:3.0
Create a configuration file extending ElasticsearchConfiguration and override clientConfiguration and elasticsearchClient creation.
During elasticsearchClient creation inject your own objectMapper configured to use Java 8 time module, which will override the default 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);
}
Maven dependency:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.14.0</version>
</dependency>
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>
Related
I have a class like the following
public class TimePeroid implements Comparable<TimePeroid>, Serializable {
private static final long serialVersionUID = -65707223409901256L;
#Expose
//#JsonFormat(pattern="HH:mm:ss")
private LocalTime start;
#Expose
//#JsonFormat(pattern="HH:mm:ss")
private LocalTime end;
//getter setter
}
At response time when I convert it into a JSON string with the help of ObjectMapper, I get a JSON string like below
{
"start" : {"hour":6,"minute":0,"second":0,"nano":0},
"end":{"hour":18,"minute":0,"second":0,"nano":0}
}
But I need it like this
{
"start":"06:00:00",
"end":"18:00:00"
}
What I've tried until now
Solution 1
spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS = false
Solution 2
#JsonFormat(pattern="HH:mm:ss")
Solution 3
ObjectMapper mapperObj = new ObjectMapper();
mapperObj.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
DateFormat df = new SimpleDateFormat("HH:mm");
mapperObj.setDateFormat(df);
None of them works. please help me on this
Have you included JavaTimeModule in your Jacckson configuration?
A related SO question & answer here https://stackoverflow.com/a/32202201/134894
To copy/paste from that answer
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.6.0</version>
</dependency>
and
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
Once you have the module registered, you could also potentially configure the default LocalDate formatting, by registering a serializer.
SimpleModule module = new SimpleModule();
module.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ISO_LOCAL_TIME));
mapper.registerModule(module);
Actually, I see you're using Spring Boot.. so this might be more idiomatic for that environment
#Bean
public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() {
return builder -> {
builder.serializers(new LocalTimeSerializer(DateTimeFormatter.ISO_LOCAL_TIME));
builder.deserializers(new LocalTimeDeserializer(DateTimeFormatter.ISO_LOCAL_TIME));
builder.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
};
}
You need to use "KK, not "HH"
Code Below:
#JsonFormat(pattern="KK:mm")
I'm using spring-boot 2.1.6 and there is an API to accept a form including a date like:
#Data
public class MyForm {
private LocalDate date;
...
}
#Controller
public class MyController {
#PostMapping("...")
public ResponseEntity<...> post(#RequestBody MyForm myForm) {
...
}
}
By default spring MVC accept this JSON format:
{
"date": [2020, 6, 17],
...
}
So in Front-End, my JavaScript code just submit a form like this, i.e. JS will convert a date to an array.
But when I run spring-boot test, this serialization does not work, with the following code:
private ObjectMapper mapper = new ObjectMapper();
#Autowired
private MockMvc mockMvc;
#Test
public void doTest() {
MyForm form = ...
MvcResult result = mockMvc.perform(MockMvcRequestBuilders.post("/...").
contentType("application/json").content(mapper.writeValueAsString(form)).andReturn();
...
}
This is because Jackson by default serialize LocalDate as:
{
"date": {
"year":2020,
"month":"JUNE",
"monthValue":6,
...
}
...
}
As mentioned here: LocalDate Serialization: date as array? , there are many configurations to force spring-boot serialize data as format yyyy-MM-dd. But I don't want to change my JS code. I just want to make my test case work.
How can I configure ObjectMapper to force Jackson to serialize LocalDate to Array? I just want to get this:
{
"date": [2020, 6, 17],
...
}
UPDATE
LocalDate here is java.time.LocalDate but not org.joda.time.LocalDate.
You need to register JavaTimeModule. Maven dependency:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
Example, how to use it:
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import java.time.LocalDate;
public class JsonApp {
public static void main(String[] args) throws Exception {
JsonMapper mapper = JsonMapper.builder()
.addModule(new JavaTimeModule())
.build();
mapper.writeValue(System.out, new MyForm());
}
}
class MyForm {
private LocalDate value = LocalDate.now();
public LocalDate getValue() {
return value;
}
public void setValue(LocalDate value) {
this.value = value;
}
}
Above code prints:
{"value":[2020,6,17]}
See also:
jackson-modules-java8
Jackson Serialize Instant to Nanosecond Issue
Jackson deserialize elasticsearch long as LocalDateTime with Java 8
You could try to create a custom deserializer for LocalDate
class LocalDateDeserializer extends StdDeserializer<LocalDate> {
#Override
public LocalDate deserialize(JsonParser parser, DeserializationContext context)
throws IOException, JsonProcessingException {
// implement;
}
}
And then register it by adding a Module bean. From the documentation:
Any beans of type com.fasterxml.jackson.databind.Module are automatically registered with the auto-configured Jackson2ObjectMapperBuilder and are applied to any ObjectMapper instances that it creates. This provides a global mechanism for contributing custom modules when you add new features to your application.
#Bean
public Module LocalDateDeserializer() {
SimpleModule module = new SimpleModule();
module.addDeserializer(LocalDate.class, new LocalDateDeserializer());
return module;
}
you can bulid a converter that gets the date value an returns the wanted array.
this will be your entity
#JsonSerialize(converter=DateToArray.class)
private LocalDate date;
your converter
#Component
public class DateToArray extends StdConverter< Date, String[]> {
#Override
public String[] convert(Date value) {
//logic for pushing data into Array and return it
}
}
ObjectMapper doesn't format ZonedDateTime object to customized one.
POJO is not under my control so that I can't change it.
I need to serialize POJO object for WS.
POJO has ZonedDateTime (I don't know why because it's date is from database).
I am using Spring-boot 2.1.8.RELEASE, so...
I put this into my dependencies:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
I also added this in application.properties:
spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS=false
In configuration file, I added this bean in configuration file because it is important that configure ObjectMapper as soon as possible to accept changes:
#Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
mapper.setTimeZone(TimeZone.getTimeZone(Locale.FRANCE.getDisplayName()));
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(ZonedDateTime.class, new ZonedDateTimeSerializer());
mapper.registerModule(javaTimeModule);
return mapper;
}
And this class:
public class ZonedDateTimeSerializer extends JsonSerializer {
public static final DateTimeFormatter FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ", Locale.FRANCE);
#Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString(((ZonedDateTime)value).format(FORMATTER));
}
I have POJO with ZonedDateTime field:
public ZonedDateTime getStartDate() {
return this.startDate != null ? this.startDate.withNano(0).withZoneSameLocal(ZoneId.of("Europe/Paris")) : null;
}
public void setStartDate(ZonedDateTime startDate) {
this.startDate = startDate != null ? startDate.withNano(0).withZoneSameLocal(ZoneId.of("Europe/Paris")) : null;
}
And in my code I autowired object mapper and serielize this POJO like this:
private final ObjectMapper mapper;
public MyClass(ObjectMapper mapper) {
this.mapper = mapper;
}
...
mapper.writeValueAsString(contactVersion);
But what I got is as follows:
"startDate":{"offset":{"totalSeconds":7200,"id":"+02:00","rules":{"transitions":[],...
and a lot of information, 82.634765625kb information for that time and all I want is:
"2019-10-15T17:00:53Z"
SOLVED: it just works.
I use IntelliJ, and I deploy under Weblogic 12.2.1.3.0 as autodeploy and it could be that I didn't run Maven clean so I just run Maven install and run it in debug mode so it was started without WRITE_DATE_AS_TIMESTAMPS=false property with old application.properties file.
I really don't know, but it works and it should be like this.
Create a getter with a return type string and mark it as a property (and mark your original getter or property as #JsonIgnored). Just like that:
#JsonGetter("startDate")
public String getStartDateAsString() {
return DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(startDate);
}
I have some questions related to JSON serialization using Jackson in a project where I use Spring Boot 2.0.0.M6, Spring Framework 5.0.1.RELEASE and Jackson 2.9.2.
I have configured the following Jackson-related settings in application.properties:
spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS=false
Serialization works mostly as I need. Nevertheless, I have noticed that Jackson seems to cut-off milliseconds if they are 000.
Test 1: Serialize Instant with milliseconds set to 000:
Initialize Instant field using Instant.parse("2017-09-14T04:28:48.000Z")
Serialize it using Jackson
Output will be "2017-09-14T04:28:48Z"
Test 2: Serialize Instant with milliseconds set to some non-000 value:
Initialize Instant field using Instant.parse("2017-09-14T04:28:48.100Z")
Serialize it using Jackson
Output will be "2017-09-14T04:28:48.100Z"
Questions:
Is that behavior by design?
Is there anything I can do to force serialization of 000?
I solve using this aproach:
ObjectMapper objectMapper = new ObjectMapper();
JavaTimeModule module = new JavaTimeModule();
module.addSerializer(Instant.class, new InstantSerializerWithMilliSecondPrecision());
objectMapper.registerModule(module);
And for InstantSerializerWithMilliSecondPrecision i used this:
public class InstantSerializerWithMilliSecondPrecision extends InstantSerializer {
public InstantSerializerWithMilliSecondPrecision() {
super(InstantSerializer.INSTANCE, false, new DateTimeFormatterBuilder().appendInstant(3).toFormatter());
}
}
Now the Instant serialization always includes milliseconds. Example: 2019-09-27T02:59:59.000Z
There appears to be a Jackson issue open for this here*. That link contains two workarounds
Workaround 1
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
SimpleModule module = new SimpleModule();
module.addSerializer(ZonedDateTime.class, new JsonSerializer<ZonedDateTime>() {
#Override
public void serialize(ZonedDateTime zonedDateTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
jsonGenerator.writeString(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZZZ").format(zonedDateTime));
}
});
objectMapper.registerModule(module);
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
Workaround 2
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(ZonedDateTime.class,
new ZonedDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSX")));
ObjectMapper mapper = new ObjectMapper().registerModule(javaTimeModule);
*Link is dead because they deprecated FasterXML/jackson-datatype-jsr310 and moved it to jackson-modules-java8. See https://github.com/FasterXML/jackson-modules-java8/issues/76
If you are trying to do this in Spring Boot and want to use #Gustavo's answer.
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.context.annotation.Configuration;
#Configuration
public class AppConfig {
#Bean
public Module javaTimeModule() {
JavaTimeModule module = new JavaTimeModule();
module.addSerializer(new InstantSerializerWithMilliSecondPrecision());
return module;
}
}
None of two workarounds mentioned by Sean Carroll works me. I end up with writing my own serializer for Instant.
final ObjectMapper mapper = new ObjectMapper();
final JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(Instant.class, new KeepMillisecondInstantSerializer());
mapper.registerModule(javaTimeModule);
public class KeepMillisecondInstantSerializer extends JsonSerializer<Instant> {
private final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSX")
.withZone(ZoneId.of("UTC"));
#Override
public void serialize(final Instant instant, final JsonGenerator jsonGenerator, final SerializerProvider serializerProvider) throws IOException {
final String serializedInstant = dateTimeFormatter.format(instant);
jsonGenerator.writeString(serializedInstant);
}
}
I guess Jackson use Instant.toString() method to serialize Instant objects by default. I also find some discussions about Instant.toString() method on StackOverflow.
Rather than fixing the bug of Jackson library, following could be a quick work around:
Create a string variable in the POJO class where you have Timestamp variable:
private Timestamp createTimeStamp;
private String stringCreateTimeStamp;
capture timestamp value as a string:
listOfPojo.forEach(pojo-> {
pojo.setStringCreateTimeStamp(request.getcreateTimeStamp().toString());
});
Refer https://www.baeldung.com/java-string-to-timestamp for conversions
Solve it by using custom serializers for LocalDateTime and ZonedDateTime classes.
My solution works for me because I use only these two classes in API responses to represent date and time! I don't use Instant or Date so pay attention on it.
#Configuration
class JacksonConfig {
#Bean
fun objectMapper(): ObjectMapper {
val mapper = ObjectMapper()
val javaTimeModule = JavaTimeModule().apply {
addSerializer(LocalDateTime::class.java, KeepMillisecondLocalDateTimeSerializer())
addSerializer(ZonedDateTime::class.java, KeepMillisecondZonedDateTimeSerializer())
}
mapper.registerModule(javaTimeModule)
return mapper
}
class KeepMillisecondZonedDateTimeSerializer : JsonSerializer<ZonedDateTime>() {
private val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSX")
#Throws(IOException::class)
override fun serialize(
value: ZonedDateTime,
jsonGenerator: JsonGenerator,
serializerProvider: SerializerProvider?
) {
jsonGenerator.writeString(formatter.format(value))
}
}
class KeepMillisecondLocalDateTimeSerializer : JsonSerializer<LocalDateTime>() {
private val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS")
#Throws(IOException::class)
override fun serialize(
value: LocalDateTime,
jsonGenerator: JsonGenerator,
serializerProvider: SerializerProvider?
) {
jsonGenerator.writeString(formatter.format(value))
}
}
}
This is my user class, and I to save ISO compliant date time in my database.
public class User {
#Id
private String id;
private String email;
#DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
private LocalDateTime loginDate;
}
Here is my Jersey controller:
#POST
#Consumes("application/json")
#Produces("application/json")
public Response create( User user) {
Map<Object, Object> apiResponse = new HashMap<Object, Object>();
Map<Object, Object> response = new HashMap<Object, Object>();
user = (User) userService.create(user);
}
How can can I consume a datetime format like this one in jersey? Is it possible to send a datatime String and create Java 8 date time object automatically?
{
"email" : "imz.mrz#gmail.com"
"loginDate" : "2015-04-17T06:06:51.465Z"
}
#
Update:
I was using Spring boot jersey, and had other jsr packages
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jersey</artifactId>
</dependency>
So I removed all the packages except from spring-boot-jersey package.
use this annotation for LocalDateTime
#JsonDeserialize(using = LocalDateTimeDeserializer.class)
This way I can consume ISODate and save ISODate() to mongodb and produce full formated mongodb LocalDateTime to frontend.
Problem solved.
Couple options I see...
Option 1:
Assuming you have JAXB annotation support with Jackson as the JSON provider...
You could use an XmlAdapter. For example
public class LocalDateTimeAdapter extends XmlAdapter<String, LocalDateTime> {
#Override
public LocalDateTime unmarshal(String dateString) throws Exception {
Instant instant = Instant.parse(dateString);
LocalDateTime dateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
return dateTime;
}
#Override
public String marshal(LocalDateTime dateTime) throws Exception {
Instant instant = dateTime.toInstant(ZoneOffset.UTC);
return DateTimeFormatter.ISO_INSTANT.format(instant);
}
}
See the Instant API for more information.
Then you can just annotate the field/property with the adapter
#XmlJavaTypeAdapter(LocalDateTimeAdapter.class)
private LocalDateTime loginDate;
You could also declare the annotation at the package level, so that all uses in the package will use the adapter, without the need to annotate. You do so in a file named package-info.java put inside the package
#XmlJavaTypeAdapters({
#XmlJavaTypeAdapter(type = LocalDateTime.class,
value = LocalDateTimeAdapter.class)
})
package thepackage.of.the.models;
import java.time.LocalDateTime;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters;
Option 2:
Use the Jackson APIs directly. Meaning, use a JsonDeserializer and JsonSerializer. For example
public class LocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
#Override
public LocalDateTime deserialize(JsonParser jp,
DeserializationContext dc) throws IOException, JsonProcessingException {
ObjectCodec codec = jp.getCodec();
TextNode node = (TextNode)codec.readTree(jp);
String dateString = node.textValue();
Instant instant = Instant.parse(dateString);
LocalDateTime dateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
return dateTime;
}
}
public class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
#Override
public void serialize(LocalDateTime dateTime, JsonGenerator jg,
SerializerProvider sp) throws IOException, JsonProcessingException {
Instant instant = dateTime.toInstant(ZoneOffset.UTC);
jg.writeString(DateTimeFormatter.ISO_INSTANT.format(instant));
}
}
You can apply this at the field/property level
#JsonSerialize(using = LocalDateTimeSerializer.class)
#JsonDeserialize(using = LocalDateTimeDeserializer.class)
public LocalDateTime loginDate;
Or at the ObjectMapper level (so you don't need to annotate everywhere)
#Provider
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {
final ObjectMapper mapper = new ObjectMapper();
public ObjectMapperContextResolver() {
SimpleModule module = new SimpleModule();
module.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer());
module.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer());
mapper.registerModule(module);
// add JAXB annotation support if required
mapper.registerModule(new JaxbAnnotationModule());
}
#Override
public ObjectMapper getContext(Class<?> type) {
return mapper;
}
}
Basically what happens, is that the MessageBodyWriter/MessageBodyReader used for ummarshalling/marshalling, will call the getContext method to get the ObjectMapper
Note:
The above solutions will parse from the format 2007-12-03T10:15:30.00Z, as documented in Instant.parse, and will serialize to the same format, as documented in DateTimeFormatter.ISO_INSTANT
The above is also assuming you are using Jackson as the Serializer. I used the below dependency (with Jersey 2.16) to test
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.16</version>
</dependency>
The dependency uses a JacksonJaxbJsonProvider for JAXB annotation support. If you are using a lower version of Jersey like 1.x, the jersey-json dependency should offer JAXB annotation support, if you enable the POJO mapping feature. Alternatively for Jersey 1.x, if you want to use Jackson 2, you can use this dependency
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>2.4.0</version>
</dependency>
which is actually what is used by jersey-media-json-jackson. So you could explicitly register the JacksonJaxbJsonProvider, or add the Jackson package (com.fasterxml.jackson.jaxrs.json) to list packages to scan
UPDATE
See Also:
Java 8 LocalDate Jackson format. There is Jackson Module that already comes with serializers for the Java 8 date/time APIs.