I'm using spring boot 2, and I have a class which has java.sql.Date and also java.sql.Timestamp properties. I need to serialize the Timestamp as nanoseconds and serialize the Date as standard format (yyyy-MM-dd).
At first the JSON result is like below :
"checkinTime": "2019-05-01T17:00:00.000+0000", // java.sql.Timestamp
"lastOrderDate":"2019-05-01" // java.sql.Date
And then I put these lines in the application.properties file
spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS: true
spring.jackson.serialization.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS: false
After that the result is like below :
"checkinTime": -2209014000000,
"lastOrderDate": 1556643600000,
What I want is like this
"checkinTime": -2209014000000, // java.sql.Timestamp
"lastOrderDate":"2019-05-01" // java.sql.Date
How can I achieve this in spring boot ??
You can always use a custom formatter on any fields or types. You have to have a custom formatter class and add that on your Object Mapper bean. It can be added in Java code or Xml config too. If you have your own view resolver, just make sure that it uses your customer object mapper.
The formatter can be like this for example:
public class CustomDateFormatter extends JsonSerializer<Date> {
private final DateTimeFormatter formatter;
public CustomDateFormatter() {
formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd").withZone(ZoneOffset.UTC);
}
#Override
public void serialize(Date value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
String str = formatter.format(value.toLocalDate());
gen.writeString(str);
}
}
And object mapper bean init with wiring up with a view resolver:
private ObjectMapper customObjectMapper() {
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(Date.class, new CustomDateFormatter());
mapper.registerModule(module);
mapper.enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
mapper.disable(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS);
return mapper;
}
private MappingJackson2JsonView jsonView() {
MappingJackson2JsonView view = new MappingJackson2JsonView();
view.setObjectMapper(customObjectMapper());
return view;
}
#Bean
public ContentNegotiatingViewResolver viewResolver() {
ContentNegotiatingViewResolver cnvr = new ContentNegotiatingViewResolver();
List<View> viewList = new ArrayList<>();
viewList.add(jsonView());
cnvr.setDefaultViews(viewList);
return cnvr;
}
Related
After visiting all pages about it where I found and tried many many ideas, none worked for me so I write this post.
My API in java 8 spring boot 2.2.0 have beans that are generated from xsd files. I recently changes the Date type to LocalDateTime (because deprecation so it's time to change).
The problem is, before, Date were display in a timestamp format (as a long) and now LocalDateTime are display in an array and I don't want it.
What I tried to solve:
I had a WebMvc annotation on a confiuration class, removed it, not solved the issue.
I added WRITE_DATES_AS_TIMESTAMPS: true in my application.yml (tried many formats on this properties)
I have a configuration class for message converters where I tried to manually force timestamps.
I wanted to add a Json annotation in my beans but as it generate from xsd, I don't know if it is possible. it is ?
Here is a snippet of the configuration class for message converters :
#Configuration
public class MVCConfigure implements WebMvcConfigurer {
#Autowired
private ObjectMapper jacksonObjectMapper;
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.removeIf(c -> c instanceof MappingJackson2HttpMessageConverter);
jacksonObjectMapper.enable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
converters.add(httpMessageConverterV1V2);
Nothing of theses solutions worked and I still get an array for my LocalDateTime (and I want a long).
Here is how I get the LocalDateTime :
private LocalDateTime getFirstLocalDateTime(final SolrDocument doc, final String key) {
LocalDateTime ldt = null;
Date solrDate = (Date) doc.getFirstValue(key);
if(solrDate != null){
ldt = solrDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
}
return ldt;
}
ps : The reason I use Date to get the Date from SolrDoc is because I can't cast to LocalDateTime directly
actual value :
"lastIndexedDate": [
2022,
1,
4,
2,
2,
2,
655000000
]
desire value :
"lastIndexedDate": 1641261722655
So, it is possible to get what I want ?
Thanks to #deHaar who gave me some hints I've found how to proceed.
I created a custom serializer :
public class LocalDateTimeSerialize extends StdSerializer<LocalDateTime> {
public LocalDateTimeSerialize(Class<LocalDateTime> t) {
super(t);
}
#Override
public void serialize(LocalDateTime localDateTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
Long l = localDateTime.toInstant(OffsetDateTime.now().getOffset())
.toEpochMilli();
jsonGenerator.writeString(l.toString());
}
}
I change my extendMessageConverters to use this custom serializer in my objectMapper :
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule()
.addSerializer(LocalDateTime.class, new LocalDateTimeSerialize(LocalDateTime.class)));
I'd rather set this config so you don't have to create a custom serializer:
spring:
jackson:
serialization:
write-dates-as-timestamps: true
That or set it in your mapper:
#Bean
public MappingJackson2HttpMessageConverter jsonConverter() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, true);
mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, true);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);
return new MappingJackson2HttpMessageConverter(mapper);
}
In the json of the post request I have several different date formats. I'm having troubled deserializing all at the same time. I've created a configuration class that will handle one or the other just fine. How do I add additional deserializers to handle the other formats?
I don't have access to the POJO to add any annotations there.
Here's an error I get for one of the dates I'm unable to deserialize
JSON parse error: Cannot deserialize value of type java.time.LocalDateTime from String "09/03/2020 10:59:48": Failed to deserialize java.time.LocalDateTime:
#Configuration
public class JacksonConfig {
#Bean
#Primary
public ObjectMapper objectMapper() {
JavaTimeModule module = new JavaTimeModule();
LocalDateTimeDeserializer localDateTimeDeserializer = new LocalDateTimeDeserializer(
DateTimeFormatter.ofPattern("MM/dd/yyyy HH:mm:ss"));
module.addDeserializer(LocalDateTime.class, localDateTimeDeserializer);
return Jackson2ObjectMapperBuilder.json().modules(module)
.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS).build();
}
}
I was able to resolve my issue by overriding the LocalDateTimeDeserializer's deserialize method. I modified the solution from Configure Jackson to parse multiple date formats
public class MultiDateDeserializer extends LocalDateTimeDeserializer {
public MultiDateDeserializer() {
this(null);
}
public MultiDateDeserializer(DateTimeFormatter formatter) {
super(formatter);
}
private static final long serialVersionUID = 1L;
private static final String[] DATE_FORMATS = new String[] { "yyyy-MM-dd'T'HH:mm:ss", "MM/dd/yyyy HH:mm:ss" };
#Override
public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
JsonNode node = p.getCodec().readTree(p);
final String date = node.textValue();
for (String DATE_FORMAT : DATE_FORMATS) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT, Locale.ROOT);
try {
return LocalDateTime.parse(date, formatter);
} catch (DateTimeParseException e) {
}
}
throw new ParseException(0,
"Unparseable date: \"" + date + "\". Supported formats: " + Arrays.toString(DATE_FORMATS));
}
}
And then in my JacksonConfig I have...
#Configuration
public class JacksonConfig {
#Bean
#Primary
public ObjectMapper objectMapper() {
JavaTimeModule module = new JavaTimeModule();
MultiDateDeserializer multiDateDeserializer = new MultiDateDeserializer();
module.addDeserializer(LocalDateTime.class, multiDateDeserializer);
return Jackson2ObjectMapperBuilder.json().modules(module)
.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS).build();
}
}
I am using SpringBoot 2.2. date format is "validFrom": "2013-12-31T18:30:00.000+0000"
But I want in number format (like 1411471800000).
In my entity I included the below code snippet which worked in Number format.
#JsonProperty("updDate")
**#JsonFormat(shape = JsonFormat.Shape.NUMBER)**
private Date updDate;
To achieve that, I will have to do in all my entities.Is there a way where I can make one change and it will apply for all date formats.
Please advise
You can use custom Serializer for Date type which will used to serialize Date type.
public class DateSerializer extends StdSerializer<Date> {
private static final long serialVersionUID = -7880057299936791237L;
public JacksonLocalDateSerializer() {
this(null);
}
public JacksonLocalDateSerializer(Class<Date> type) {
super(type);
}
#Override
public void serialize(Date value, JsonGenerator jsonGenerator,
SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
jsonGenerator.writeNumber(value.getTime());
}
}
and add it in object mapper so that Date type object always serialize using your custom serializer
#Configuration
public class JacksonConfig {
#Bean
#Primary
public ObjectMapper configureObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(Date.class, new DateSerializer());
objectMapper.registerModule(javaTimeModule);
return objectMapper;
}
}
I have a spring rest service that accepts and gives json output.
#PostMapping(path = "/path", consumes = {"application/json"}, produces = {"application/json"})
public ResponseEntity<RequestData> method(#RequestBody RequestData request){
return request;
}
RequestData contains several dates (XMLGregorianCalendar). I cannot change the type, since it is generated from xsd. To get dates with the original time zones, I used the parameter
spring.jackson.date-format: yyyy-MM-dd'T'HH:mm:ssZ
Request
{
"date1":"2020-02-28T09:26:59+09:00",
"date2":"2020-01-10T12:46:29+04:00",
"date3":"2020-03-15T11:32:43+08:00"
}
From this request, I got an XMLGregorianCalendar with different time zones.
But when sending a response message, the dates are converted to 0 time zone.
Response
{
"date1":"2020-02-28T00:26:59+0000",
"date2":"2020-01-10T08:46:29+0000",
"date3":"2020-03-15T03:32:43+0000"
}
What settings need to be done on jackson to get non-zero time zones in the response? It is necessary that the response time zones returned in the request.
Or maybe jackson does not know how to do this and always converts the date to a single time zone? In that case, which library to use?
Thanks!
Solution
You must create a serializer and deserializer. Then you need to override the existing ObjectMapper.
If only the serializer is overrided, then upon receipt of the data, the time zone will be normalized (reduced to +00:00), therefore it is also necessary to override the deserializer.
Serializer:
public class XMLGCSerializer extends JsonSerializer<XMLGregorianCalendar> {
#Override
public void serialize(XMLGregorianCalendar value,
JsonGenerator gen,
SerializerProvider serializers)
throws IOException {
gen.writeObject(value.toString());
}
}
Deserializer:
public class XMLGCDeserializer extends JsonDeserializer<XMLGregorianCalendar> {
#Override
public XMLGregorianCalendar deserialize(JsonParser parser, DeserializationContext context) throws IOException {
String stringDate = parser.getText();
try {
return DatatypeFactory.newInstance().newXMLGregorianCalendar(stringDate);
} catch (Exception e) {
throw new RuntimeException(e);
//or return null
}
}
}
Override ObjectMapper
#Component
public class JacksonConfig {
private final ObjectMapper objectMapper;
public JacksonConfig() {
objectMapper = new ObjectMapper();
SimpleModule s = new SimpleModule();
s.addSerializer(XMLGregorianCalendar.class, new XMLGCSerializer());
s.addDeserializer(XMLGregorianCalendar.class, new XMLGCDeserializer());
objectMapper.registerModule(s);
}
#Bean
public ObjectMapper getContext() {
return objectMapper;
}
}
You can create a seperate class to handle serialization by yourself. Here is an example:
class XMLGCSerializer extends JsonSerializer<XMLGregorianCalendar> {
#Override
public void serialize(XMLGregorianCalendar value,
JsonGenerator gen,
SerializerProvider serializers)
throws IOException {
gen.writeObject(value.toString());
}
}
Now you just need to annotate your fields in RequestData:
class RequestData{
#JsonSerialize(using = XMLGCSerializer.class)
XMLGregorianCalendar date1;
//...
}
Jackson's JavaTimeModule serialize/deserializejava.time well globally, but its default date-time format is ISO standard, like 2018-01-10T10:20:30 for LocalDateTime and 2018-01-10T10:20:30+08:00 for OffsetDateTime. But I need to set a global local format like 2018-01-10 10:20:30 for LocalDateTime and OffsetDateTime, without T and OffsetTime (use local default OffsetTime). How can I do this?
Notes: I know about #JsonFormat, #JsonSerialize and #JsonDeserialize. That is not global setting.
Spring boot
#SpringBootApplication
public class Application implements Jackson2ObjectMapperBuilderCustomizer {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Override
public void customize(Jackson2ObjectMapperBuilder builder) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTimeSerializer localDateTimeSerializer = new LocalDateTimeSerializer(formatter);
builder.failOnEmptyBeans(false) // prevent InvalidDefinitionException Error
.serializerByType(LocalDateTime.class, localDateTimeSerializer);
}
}
Springboot & Spring Framework
#Configuration
public class WebMvcConfig implements WebMvcConfigurer {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTimeSerializer localDateTimeSerializer = new LocalDateTimeSerializer(formatter);
LocalDateTimeDeserializer localDateTimeDeserializer = new LocalDateTimeDeserializer(formatter);
JavaTimeModule module = new JavaTimeModule();
module.addSerializer(LocalDateTime.class, localDateTimeSerializer);
module.addDeserializer(LocalDateTime.class, localDateTimeDeserializer);
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);
// add converter at the very front
// if there are same type mappers in converters, setting in first mapper is used.
converters.add(0, new MappingJackson2HttpMessageConverter(mapper));
}
}
hope this could help you.
If you are using a single instance of ObjectMapper globally, (and want a solution independent of Spring/Java 8 jackson modules,) you can do something like:
public ObjectMapper getCustomConfigMapper() {
final ObjectMapper mapper = new ObjectMapper();
final SimpleModule module = new SimpleModule();
module.addDeserializer(LocalDateTime.class, new CustomLocaDateTimeDeserializer());
mapper.registerModule(module);
return mapper;
}
public static class CustomLocaDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
public static final DateTimeFormatter CUSTOM_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
#Override
public LocalDateTime deserialize(JsonParser jsonParser, DeserializationContext __) throws IOException {
final String value = jsonParser.getText().strip();
return LocalDateTime.parse(value, CUSTOM_FORMATTER);
}
}
You can configure an ObjectMapper. This page explains how https://www.baeldung.com/jackson-serialize-dates. I think you want something close to example 4 on that page.
Then you need to make that the global ObjectMapper. Different frameworks use different methods. This page explains how to do that in Spring and Spring-boot Configuring ObjectMapper in Spring. For others just Google for it.