wrong timezone with clock and jackson - java

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

Related

ZonedDateTime returning as Epoch time instead of standard String in Spring App

I am trying fetch some DateTime values stored in a local MySQL database in my Spring App. These dates are parsed into a ZoneDateTime and are then sent to a Client Front End as a json. I have an Object Mapper that specifies this conversion.
#Bean
public ObjectMapper objectMapper() {
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(ZonedDateTime.class,
new ZonedDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX")));
return Jackson2ObjectMapperBuilder.json().featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) // ISODate
.modules(javaTimeModule).build();
}
However, on the front-end, the values I receive are in Epoch time instead of the format specified in the ObjectMapper. I have checked the value parsed into ZoneDateTime and it is parsed correctly. My guess is that there is some fault in the process mapping the ZoneDateTime object into the json String value.
What could be the fix of this?
Here is how to do it simply and efficiently:
#JsonFormat(shape= JsonFormat.Shape.STRING, pattern="EEE MMM dd HH:mm:ss Z yyyy")
#JsonProperty("created_at")
ZonedDateTime created_at;
This is a quote from a question: [Jackson deserialize date from Twitter to `ZonedDateTime`
Also, I don't think that you need to add a special serializer for this. It works for me without this definition just fine.
https://docs.spring.io/spring/docs/4.3.0.RC1_to_4.3.0.RC2/Spring%20Framework%204.3.0.RC2/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.html#timeZone-java.lang.String-
Docs says there is a timezone(String) method to override default timezone.
I suppose you could pass the timezone into this method while building the ObjectMapper
#Bean
public Jackson2ObjectMapperBuilderCustomizer init() {
return new Jackson2ObjectMapperBuilderCustomizer() {
#Override
public void customize(Jackson2ObjectMapperBuilder builder) {
builder.timeZone(TimeZone.getDefault());
}
};
}
You could use above code to override default timezone.
return Jackson2ObjectMapperBuilder.json().featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) // ISODate
.modules(javaTimeModule).timeZone(TimeZone.getDefault()).build();
You could try this.

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

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

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.

Categories