Jackson date-format for OffsetDateTime in Spring Boot - java

I'm trying to output an OffsetDateTime from my Spring application, and have in my application.properties these properties:
spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS=false
spring.jackson.date-format=yyyy-MM-dd'T'HH:mm
However when the date is returned it is formatted as
"2017-01-30T16:55:00Z"
How should I correctly configure the format for the date in my Spring application?

So I've managed to figure out a solution, but if you have an alternative please post it.
I ended up creating a new primary ObjectMapper bean, and registering a new module with a custom serializer for OffsetDateTime. I'm able to set my own date format in here, using java.time.format.DateTimeFormatter. I also had to register the JavaTimeModule with my mapper.
#Configuration
public class JacksonOffsetDateTimeMapper{
#Primary
#Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(OffsetDateTime.class, new JsonSerializer<OffsetDateTime>() {
#Override
public void serialize(OffsetDateTime offsetDateTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
jsonGenerator.writeString(DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(offsetDateTime));
}
});
objectMapper.registerModule(simpleModule);
return objectMapper;
}
}

ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
By doing that, you can get OffsetDateTime properties as ISO 8601 including offset in your target.

Adding a dependency on jackson-modules-java8 worked for me (jackson-datatype-jsr310 is deprecated)
<!-- deserialize Java 8 date time types e.g OffsetDateTime -->
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-modules-java8</artifactId>
</dependency>
I also needed to add this for it to work:
om.registerModule(new JavaTimeModule());
No need for the write-dates-as-timestamps=false or om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) - that is applicable for Java "Date" object.
I used this annotation:
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
and get output like this:
"timestamp":"2020-04-23T08:00:00.000-06:00"

Add jackson-datatype-jsr310 to your dependencies
Add to application.properties:
spring.jackson.serialization.write-dates-as-timestamps=false
You will get:
"lastUpdated": "2017-07-16T19:17:57.689Z"

The spring property doesn't work for me as well. Setting the property to ObjectMapper works for me though.
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);

Have you tried put #JsonFormat(pattern="dd/MM/yyyy HH:mm:ss Z") before your field?
#JsonProperty("timestamp")
#JsonFormat(pattern="yyyy-MM-dd'T'HH:mm")
private OffsetDateTime timestamp;
I guess you will get:
2017-01-30'T'16:55

Removing #EnableWebMvc and just inheriting from WebMvcConfigurerAdapter helped me not to overwrite the ObjectMapper config, so that the configuration properties specified in the application.yml are applied.

Related

Serialize JSON date format from ZonedDateTime to custom date format using ObjectMapper

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);
}

How do I configure Jackson Serialization on LocalDateTime and LocalDate for Java?

I know that there are many posts on Stackoverflow regarding this topic and I'm sure I've read just about all of them, but I am still struggling to get this working and would appreciate any guidance.
This is the Spring Parent and Jackson Dependencies I am using:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
</parent>
<!-- 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>
Within my Application Configuration I am trying to use:
#Autowired
void configureJackson(ObjectMapper jackson2ObjectMapper) {
JavaTimeModule timeModule = new JavaTimeModule();
timeModule.addDeserializer(LocalDate.class,
new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
timeModule.addDeserializer(LocalDateTime.class,
new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
timeModule.addSerializer(LocalDate.class,
new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
timeModule.addSerializer(LocalDateTime.class,
new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
jackson2ObjectMapper
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
.registerModule(timeModule)
.registerModule(new ParameterNamesModule())
.registerModule(new Jdk8Module())
.registerModule(new JtsModule());
}
This is the model I am testing with. (I am trying to achive the same in 5-6 models).
public class DateRange implements Serializable {
private static final long serialVersionUID = 6412487507434252330L;
private LocalDateTime startTime;
private LocalDateTime endTime;
private LocalDate startDate;
private LocalDate endDate;
// Getters/Setters
I am hoping that I could find an approach that would apply globally without the need for me to annotate each field individually. I was using java.util.Date before, but ran into some issues with other functionality there.
Trying to use these newer (better) date models instead of that old damaged one is causing me a lot of headache over a simple thing.
Update
I changed my configuration to one of the suggested below and came really close to solving this issue.
#Bean
#Primary
public ObjectMapper objectMapper() {
JavaTimeModule timeModule = new JavaTimeModule();
timeModule.addDeserializer(LocalDate.class,
new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
timeModule.addDeserializer(LocalDateTime.class,
new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
timeModule.addSerializer(LocalDate.class,
new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
timeModule.addSerializer(LocalDateTime.class,
new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
return new ObjectMapper()
//.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
.registerModule(timeModule)
.registerModule(new ParameterNamesModule())
.registerModule(new Jdk8Module())
.registerModule(new JtsModule());
}
Now the only issue I have is that LocalDateTime is printing timestamps like: "2018-07-26T07:57:12.938" when I need them like: 2018-07-26 07:57:12.
These fields are already in-use today and I need to make this change seamless in a way that doesn't require my API consumers to make any adjustments.
Cassio mentioned that disabling SerializationFeature.WRITE_DATES_AS_TIMESTAMPS will print timestamps in this ISO format, but it's not what I need. I tried to comment out the field in the hopes that it would pickup the custom DateTimeFormatter I am providing, however it has not changed my output.
The JavaTimeModule will do the hard work for you. It provides a set of serializers and deserializers for for the java.time types. If the SerializationFeature.WRITE_DATES_AS_TIMESTAMPS is disabled, java.time types will be serialized in standard ISO-8601 string representations.
By default, Spring will provide you with an instance of ObjectMappper created by the Jackson2ObjectMapperBuilder. This instance is auto-configured for you. See the class documentation for details.
If you want to replace the default ObjectMapper completely, either define a #Bean of that type and mark it as #Primary:
#Bean
#Primary
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
return mapper;
}
Or, if you prefer the builder-based approach, define a Jackson2ObjectMapperBuilder #Bean.
#Bean
public Jackson2ObjectMapperBuilder objectMapperBuilder() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.modules(new JavaTimeModule());
builder.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
return builder;
}
Note that, in either case, doing so disables all auto-configuration of the ObjectMapper. See the documentation for details.
I finally got this working with the help of Cassio and some other digging on the web. Looking back I think the issue was that I was defining the custom formats on the JavaTimeModule, but I needed to define them on the mapper instead.
#Bean
public Jackson2ObjectMapperBuilder objectMapperBuilder() {
return new Jackson2ObjectMapperBuilder()
.createXmlMapper(false)
.indentOutput(true)
.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.serializers(
new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")),
new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")))
.deserializers(
new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")),
new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")))
.modules(
new JavaTimeModule(),
new ParameterNamesModule(),
new Jdk8Module(),
new JtsModule());
}
https://github.com/spring-projects/spring-boot/issues/4217
http://www.baeldung.com/jackson-serialize-dates
You can also solve this to expose this instance:
#Bean
public Jackson2ObjectMapperBuilder configureObjectMapper() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.modules(new JavaTimeModule(),new ParameterNamesModule(),new Jdk8Module());
builder.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
return builder;
}

Jackson change timestamp format

I'm outputting some database results via json webservice. Simple as:
#GetMapping(produces = "application/json")
public List<Map<String, Object>> get(Param params) {
return jdbcTemplate.queryForList(sql, params)
}
Problem: java.sql.Timestamp is converted to format 2018-04-26T07:52:02.000+0000, while the plain database output would be 2018-04-26 07:52:02.0.
Question: is there any configuration property to tell spring to just pass through the native timestamp received from the database, instead of converting it with jackson logic?
I want to change the java.sql.Timestamp format globally.
Important: please don't suggest any annotations! I don't have any bean/pojo, I'm just returning the plain database result as a Map.
I want to change the java.sql.Timestamp format globally.
Set a date format to your ObjectMapper instance:
ObjectMapper mapper = new ObjectMapper();
mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S"));
In Spring applications, you can expose the ObjectMapper instance as a bean:
#Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S"));
return mapper;
}
In Spring Boot you can use the property spring.jackson.date-format to define the date format:
spring.jackson.date-format: yyyy-MM-dd HH:mm:ss.S
For more details on the common application properties, refer to the documentation.
Consider the following code:
Map<String, Object> data = new HashMap<>();
data.put("date", new Timestamp(ZonedDateTime.now().toInstant().toEpochMilli()));
System.out.println(mapper.writeValueAsString(data));
It will print:
{"date":"2018-04-26 07:25:14.408"}
Or if you need this as a Spring #Bean
#Bean
public JacksonProperties jacksonProperties() {
JacksonProperties properties = new JacksonProperties();
properties.setDateFormat("yyyy-MM-dd'T'HH:mm:ss"); // put any pattern you need
return properties;
}

Force Milliseconds When Serializing Instant to ISO8601 using Jackson

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))
}
}
}

How to set format of string for java.time.Instant using objectMapper?

I have an entity with java.time.Instant for created data field:
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#EqualsAndHashCode
public class Item {
private String id;
private String url;
private Instant createdDate;
}
I am using com.fasterxml.jackson.databind.ObjectMapper to save item to Elasticsearch as JSON:
bulkRequestBody.append(objectMapper.writeValueAsString(item));
ObjectMapper serializes this field as an object:
"createdDate": {
"epochSecond": 1502643595,
"nano": 466000000
}
I was trying the annotation #JsonFormat(shape = JsonFormat.Shape.STRING) but it doesn't work for me.
My question is how I could serialize this field as 2010-05-30 22:15:52 string?
One solution is to use jackson-modules-java8. Then you can add a JavaTimeModule to your object mapper:
ObjectMapper objectMapper = new ObjectMapper();
JavaTimeModule module = new JavaTimeModule();
objectMapper.registerModule(module);
By default the Instant is serialized as the epoch value (seconds and nanoseconds in a single number):
{"createdDate":1502713067.720000000}
You can change that by setting in the object mapper:
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
This will produce the output:
{"createdDate":"2017-08-14T12:17:47.720Z"}
Both formats above are deserialized without any additional configuration.
To change the serialization format, just add a JsonFormat annotation to the field:
#JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "UTC")
private Instant createdDate;
You need to set the timezone, otherwise the Instant can't be serialized properly (it throws an exception). The output will be:
{"createdDate":"2017-08-14 12:17:47"}
Another alternative, if you don't want to (or can't) use java8 modules, is to create a custom serializer and deserializer, using a java.time.format.DateTimeFormatter:
public class MyCustomSerializer extends JsonSerializer<Instant> {
private DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneOffset.UTC);
#Override
public void serialize(Instant value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
String str = fmt.format(value);
gen.writeString(str);
}
}
public class MyCustomDeserializer extends JsonDeserializer<Instant> {
private DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneOffset.UTC);
#Override
public Instant deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
return Instant.from(fmt.parse(p.getText()));
}
}
Then you annotate the field with those custom classes:
#JsonDeserialize(using = MyCustomDeserializer.class)
#JsonSerialize(using = MyCustomSerializer.class)
private Instant createdDate;
The output will be:
{"createdDate":"2017-08-14 12:17:47"}
One detail is that in the serialized string you're discarding the fraction of second (everything after the decimal point). So, when deserializing, this information can't be recovered (it'll be set to zero).
In the example above, the original Instant is 2017-08-14T12:17:47.720Z, but the serialized string is 2017-08-14 12:17:47 (without the fraction of seconds), so when deserialized the resulting Instant is 2017-08-14T12:17:47Z (the .720 milliseconds are lost).
For those looking to parse Java 8 timestamps. You need a recent version of jackson-datatype-jsr310 in your POM and have the following module registered:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
To test this code
#Test
void testSeliarization() throws IOException {
String expectedJson = "{\"parseDate\":\"2018-12-04T18:47:38.927Z\"}";
MyPojo pojo = new MyPojo(ZonedDateTime.parse("2018-12-04T18:47:38.927Z"));
// serialization
assertThat(objectMapper.writeValueAsString(pojo)).isEqualTo(expectedJson);
// deserialization
assertThat(objectMapper.readValue(expectedJson, MyPojo.class)).isEqualTo(pojo);
}
Here's some Kotlin code of formatting Instant, so it does not contain milliseconds, you can use custom date formatters
ObjectMapper().apply {
val javaTimeModule = JavaTimeModule()
javaTimeModule.addSerializer(Instant::class.java, Iso8601WithoutMillisInstantSerializer())
registerModule(javaTimeModule)
disable(WRITE_DATES_AS_TIMESTAMPS)
}
private class Iso8601WithoutMillisInstantSerializer
: InstantSerializer(InstantSerializer.INSTANCE, false, DateTimeFormatterBuilder().appendInstant(0).toFormatter())
You need to add below dependency
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.6.5</version>
</dependency>
And then register the modules as below :
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.findAndRegisterModules();
In my case it was enough to register the JavaTimeModule:
ObjectMapper objectMapper = new ObjectMapper();
JavaTimeModule module = new JavaTimeModule();
objectMapper.registerModule(module);
messageObject = objectMapper.writeValueAsString(event);
In the event Object I have a field of type Instant.
In the deserialization you also need to register the java time module:
ObjectMapper objectMapper = new ObjectMapper().registerModule(new JavaTimeModule());
Event event = objectMapper.readValue(record.value(), Event.class);
You can use Spring ObjectMapper which already configured with JavaTimeModule. Just inject it from Spring context and don't use new ObjectMapper().
If using Spring, and spring-web is on the classpath, you can create an ObjectMapper using the Jackson2ObjectMapperBuilder. It registers the following commonly used modules within the method registerWellKnownModulesIfAvailable.
com.fasterxml.jackson.datatype.jdk8.Jdk8Module
com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
com.fasterxml.jackson.datatype.joda.JodaModule
com.fasterxml.jackson.module.kotlin.KotlinModule
Some of these modules have been merged into Jackson 3; see here.

Categories