Jackson change timestamp format - java

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

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

Redisson (through JCache API) deserialising only to String

I have a Redisson client to store a pair String, LocalDateTime. It's configured to be used through the JCache API (JSR-107).
The storage is done OK, converting with Jackson to values like 2018-01-23T11:59:34.997834 but for retrieval is not using any converter and returning a String, giving a ClassCastException in the cache#get invocation.
What am I missing here?
#Test
public void getCacheInline() {
Config redissonCfg = new Config();
redissonCfg
.setCodec(new JsonJacksonCodec(buildObjectMapper()))
.useSingleServer()
.setAddress("redis://redis:6379");
MutableConfiguration<String, LocalDateTime> jcacheConfig = new MutableConfiguration<String, LocalDateTime>()
.setTypes(String.class, LocalDateTime.class)
.setExpiryPolicyFactory((Factory<ExpiryPolicy>) () -> new CreatedExpiryPolicy(new Duration(SECONDS, 100)));
Configuration<String, LocalDateTime> configuration = RedissonConfiguration.fromConfig(redissonCfg, jcacheConfig);
Cache<String, LocalDateTime> cache = cacheManager.createCache(CACHE_NAME, configuration);
LocalDateTime expectedDateTime = LocalDateTime.now();
cache.put("testKey", expectedDateTime);
// In this line: java.lang.ClassCastException: java.base/java.lang.String cannot be cast to java.base/java.time.LocalDateTime
LocalDateTime actualDateTime = cache.get("testKey");
assertThat(actualDateTime, is(equalTo(expectedDateTime)));
}
private ObjectMapper buildObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.configure(FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure(WRITE_DATES_AS_TIMESTAMPS, false);
objectMapper.configure(READ_DATE_TIMESTAMPS_AS_NANOSECONDS, false);
objectMapper.setSerializationInclusion(NON_NULL);
return objectMapper;
}
Initial workaround was instead of storing a pair String, LocalDateTime, wrapping the LocalDateTime in a wrapper class:
public class LocalDateTimeWrapper {
private LocalDateTime value;
...
}
This would make Jackson serialize a json string with a #class property signalling the LocalDateTimeWrapper class, and from there LocalDateTime can be retrieved as the type to deserialize the string like 2018-01-23T11:59:34.997834 to.
Better solution that I've tried and worked for me is this one suggested in GitHub issue https://github.com/redisson/redisson/issues/1260#issuecomment-367272400 extending JsonJacksonMapCodec like this:
public static class ExtendedJsonJacksonMapCodec extends JsonJacksonMapCodec {
public ExtendedJsonJacksonMapCodec() {
super(String.class, LocalDateTime.class);
}
#Override
protected void init(ObjectMapper objectMapper) {
objectMapper.registerModule(new JavaTimeModule());
super.init(objectMapper);
}
}
and then linking it from config like this (YAML format):
singleServerConfig:
address: "redis://localhost:6379"
codec: !<com.example.ExtendedJsonJacksonMapCodec> {}

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.

Jackson date-format for OffsetDateTime in Spring Boot

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.

wrong timezone with clock and jackson

I'm using jackson to de-/serialize dtos. I made some custom configuration for jacksons ObjectMapper and Clock as the following code shows:
#Bean
public ObjectMapper objectMapper()
{
ObjectMapper mapper = new ObjectMapper();
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
dateFormat.setTimeZone(TimeZone.getTimeZone("Europe/Berlin"));
mapper.setDateFormat(dateFormat);
return mapper;
}
#Bean
#Profile("!testing")
public Clock clock()
{
return Clock.systemDefaultZone();
}
For testing I defined my custom clock (SettableClock):
#Bean
#Profile("testing")
public Clock clock()
{
return new SettableClock(); // extends Clock
}
In my database I store some dates in the following format: yyyy-MM-dd HH:mm:ss.
When I create a new database entry I get the following result:
1 | 2016-10-16 18:42:55 (german time, UTC+2) which is correct.
This Date will be created from an Instant, using the defined clock:
Instant now = Instant.now(clock);
return new Timestamp(now.toEpochMilli());
My problem is now that jackson converts the date in a wrong way and not as defined in the object mapper configuration. Eg. 2016-10-16T16:42:55Z which is not UTC+2..
I discovered this problem when writing a test, because in my test when I create a new Instant using Instant.now() I'm getting the time from UTC+2 (local time). Same result when using new Date() in js for example.
As I understand the jackson documentation my object mapper configuration seems valid. But why jackson doesn't make use of it? In debug mode I can see that my configuration will applied.
This code shows how I include the configured object mapper:
#Configuration
public class WebMvcConfiguration extends WebMvcConfigurerAdapter
{
#Autowired
private ObjectMapper objectMapper;
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters)
{
MappingJackson2HttpMessageConverter jacksonConverter = new MappingJackson2HttpMessageConverter();
jacksonConverter.setObjectMapper(objectMapper);
converters.add(jacksonConverter);
super.configureMessageConverters(converters);
}
}
Does anyone know why I'm still getting a wrong time zone?
When serializing a date using +02:00 everything works as expected. Just the deserialization doesn't work :(
I also tried to configure the clock especially to create times in UTC+2 but this doesn't change anything. The problem seems to be located in my object mapper I guess...
Regards, Tim

Categories