Spring boot Jackson Deserializer for ZonedDateTime Not working - spring

I have overridden configuration for objectMapper.
#Bean
#Primary
public ObjectMapper serializingObjectMapper() {
return getObjectMapper();
}
public static ObjectMapper getObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
JavaTimeModule javaTimeModule = new JavaTimeModule();
DateTimeFormatter dateTimeFormatter = ofPattern("yyyy-MM-dd'T'HH:mm:ss");
DateTimeFormatter dateFormatter = ofPattern("yyyy-MM-dd");
javaTimeModule.addSerializer(LocalDateTime.class,
new LocalDateTimeSerializer(dateTimeFormatter));
javaTimeModule.addDeserializer(LocalDateTime.class,
new LocalDateTimeDeserializer(dateTimeFormatter));
javaTimeModule.addSerializer(LocalDate.class,
new LocalDateSerializer(dateFormatter));
javaTimeModule.addDeserializer(LocalDate.class,
new LocalDateDeserializer(dateFormatter));
javaTimeModule.addSerializer(ZonedDateTime.class, ZonedDateTimeSerializer.INSTANCE);
javaTimeModule.addDeserializer(ZonedDateTime.class, InstantDeserializer.ZONED_DATE_TIME);
objectMapper.registerModule(javaTimeModule);
objectMapper.setSerializationInclusion(Include.NON_NULL);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return objectMapper;
}
I have LocalDateTime and ZonedDateTime. LocalDateTime correctly coming. But ZonedDateTimeis not coming in ISO format
"zonedIdField": 1658829622.000000000,
"localDateTimeField": "2022-12-15T15:35:27",
Am I missing something. ZonedDateTimeis coming as number

Related

JmsMessagingTemplate is message converter broken?

I was trying to use a JmsMessagingTemplate with a JmsTemplate that has a org.springframework.jms.support.converter.MappingJackson2MessageConverter but the problem is that navigating up on the JmsMessagingTemplate to org.springframework.messaging.core.AbstractMessageSendingTemplate where the converter used is an implementation of org.springframework.messaging.converter.MessageConverter which doesn't seem compatible with org.springframework.jms.support.converter.MappingJackson2MessageConverter.
Is this broken or I'm trying to do something wrong here?
You haven't added your configuration code. So assuming that you have not added bean for message converter. Please find below code snippet for the configuration, hope that will resolve the problem.
#Bean
public MessageConverter jacksonJmsMessageConverter() {
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.setTargetType(MessageType.TEXT);
converter.setTypeIdPropertyName("_type");
converter.setObjectMapper(objectMapper());
return converter;
}
#Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
return objectMapper;
}
#Bean
public JmsTemplate jmsTemplate(ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
JmsTemplate jmsTemplate = new JmsTemplate();
jmsTemplate.setConnectionFactory(connectionFactory());
jmsTemplate.setMessageConverter(jacksonJmsMessageConverter());
return jmsTemplate;
}
#Bean
public ConnectionFactory connectionFactory() {
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory();
connectionFactory.setBrokerURL(url);
connectionFactory.setUserName(user);
connectionFactory.setPassword(password);
return connectionFactory;
}
Since I'm wrapping around a jmsMessagingTemplate I had to set the converter explicitly like this:
public DelegatingJmsMessagingTemplate(JmsTemplate jmsTemplate) {
this.jmsMessagingTemplate = new JmsMessagingTemplate(jmsTemplate);
final var messagingMessageConverter = new MessagingMessageConverter(jmsTemplate.getMessageConverter());
this.jmsMessagingTemplate.setJmsMessageConverter(messagingMessageConverter); //seems to do the trick
this.jmsMessagingTemplate.setDefaultDestinationName(jmsTemplate.getDefaultDestinationName());
}
now both publisher and subscriber convert the message accordingly.
As I mention in a comment, I found out that org.springframework.jms.core.JmsMessagingTemplate have a org.springframework.jms.core.JmsMessagingTemplate.MessagingMessageCreator where the real conversion happens.

Override JSON properties in #RestController in Spring 4.3.3 WebMVC

I want to override the following properties in Spring RestController in WebMVC.
objectMapper.setSerializationInclusion(Include.NON_NULL);
objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
objectMapper.setDateFormat(DATEFORMAT);
I've tried the following in multiple combinations, but nothing worked. Still getting the null value without root name in the response.
#EnableWebMvc
public class ApplicationContextConfig extends WebMvcConfigurerAdapter {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
//MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setSerializationInclusion(Include.NON_NULL);
objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
objectMapper.setDateFormat(DATEFORMAT);
//jsonConverter.setObjectMapper(objectMapper);
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter(objectMapper);
//jsonConverter.setJsonPrefix(jsonPrefix);
converters.add(jsonConverter);
}
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
for (HttpMessageConverter<?> converter : converters) {
if (converter instanceof MappingJackson2HttpMessageConverter) {
MappingJackson2HttpMessageConverter jsonMessageConverter = (MappingJackson2HttpMessageConverter) converter;
ObjectMapper objectMapper = jsonMessageConverter.getObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setSerializationInclusion(Include.NON_NULL);
objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
objectMapper.setDateFormat(DATEFORMAT);
break;
}
}
}
#Bean
#Primary
public ObjectMapper getObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(Include.NON_NULL);
objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
objectMapper.setDateFormat(DATEFORMAT);
return objectMapper;
}
}
Reponse:
{
"Message": null,
"Admin": null,
"Company": null
}
You should "substitute" the object mapper bean provided by spring with your own and configure it.
#Configuration
public class MyJacksonCustomConfiguration {
#Bean
public ObjectMapper objectMapper() { // note the name should be 'objectMapper'
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(Include.NON_NULL);
objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE, true);
objectMapper.setDateFormat(DATEFORMAT);
return objectMapper;
}
}
All in all, make sure that only one bean of type object mapper should exist in the application context. Apart of fact that its a pretty "heavy" object, if spring will inject a wrong mapper you won't be able to benefit from customization that you've done.

Spring Data: Saving LocalTime to MongoDB using CustomConversions

How do I only convert the string in HH:mm format to LocalTime and the other way?
Here is my mongo config and right now it is converting any fields that are of String type to LocalTime and failed in the process because some of them are not meant to be LocalTime... but some of them are LocalTime in String format, so those actually need to get converted to LocalTime
#Bean
public MappingMongoConverter mappingMongoConverter() throws Exception {
MappingMongoConverter converter = new MappingMongoConverter(NoOpDbRefResolver.INSTANCE, this.mongoMappingContext());
converter.setCustomConversions(this.customConversions());
converter.afterPropertiesSet();
return converter;
}
#Bean
public MongoMappingContext mongoMappingContext() {
MongoMappingContext context = new MongoMappingContext();
context.setSimpleTypeHolder(new SimpleTypeHolder(new HashSet<>(Arrays.asList(
LocalTime.class
)), MongoSimpleTypes.HOLDER));
return context;
}
#Bean
public MongoCustomConversions customConversions() {
List<Converter<?, ?>> converters = new ArrayList<>();
converters.add(new LocalTimeToStringConverter());
converters.add(new StringToLocalTimeConverter());
return new MongoCustomConversions(converters);
}

SpringBoot TestRestTemplate and LocalDateTime not working

I am using LocalDateTime in my model, after including LocalDateTimeDeserializer,
converted the bean field to
#NotNull
#Column(name = "created")
#JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime created;
and included the
spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS = false
property in the SpringBoot's application.properties file, the application is finally able to deserialize the JSON and show properly like,
"created": "2018-04-22T21:21:53.025",
But, when I am doing testing, it ignores the WRITE_DATES_AS_TIMESTAMPS flag, I guess and generates an output for the same string date above like,
"created":{"year":2018,"monthValue":4,"month":"APRIL","dayOfMonth":22,"dayOfYear":112,"dayOfWeek":"SUNDAY","hour":21,"minute":23,"second":16,"nano":986000000,"chronology":{"id":"ISO","calendarType":"iso8601"}}
Please, note that including the property in application.properties in test resources folder did not change anything.
My test configuration looks like,
#SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#ContextConfiguration
#Category(IntegrationTest.class)
public class ApplicationTests {
....
Do you have any idea on what I am doing wrong?
I had the same issue and the below solution worked for me,
add the below code in your Applicationtests class
protected MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter;
#Autowired
public void setConverters(HttpMessageConverter<?>[] converters) {
this.mappingJackson2HttpMessageConverter = (MappingJackson2HttpMessageConverter)Arrays.asList(converters).stream()
.filter(hmc -> hmc instanceof MappingJackson2HttpMessageConverter)
.findAny()
.orElse(null);
assertNotNull("the JSON message converter must not be null",
this.mappingJackson2HttpMessageConverter);
final ObjectMapper objectMapper = new ObjectMapper();
final JavaTimeModule javaTimeModule = new JavaTimeModule();
objectMapper.registerModule(new Jdk8Module());
objectMapper.registerModule(new ParameterNamesModule(JsonCreator.Mode.PROPERTIES));
mappingJackson2HttpMessageConverter.setObjectMapper(objectMapper);
}
If you want to support your own date formats then add the formatters as well,
//the below customisation is required if you need to support different date formats
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeCustomSerializer());
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeCustomDeserializer());
objectMapper.registerModule(javaTimeModule);
Where the custom classes will looks like,
public class LocalDateTimeCustomSerializer extends LocalDateTimeSerializer {
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MMM-dd HH:mm:ss");
LocalDateTimeCustomSerializer() {
this(FORMATTER);
}
public LocalDateTimeCustomSerializer(DateTimeFormatter f) {
super(f);
}
#Override
protected DateTimeFormatter _defaultFormatter() {
return FORMATTER;
}
}
and
public class LocalDateTimeCustomDeserializer extends LocalDateTimeDeserializer {
public LocalDateTimeCustomDeserializer() {
this(DateTimeFormatter.ofPattern("yyyy-MMM-dd HH:mm:ss"));
}
public LocalDateTimeCustomDeserializer(DateTimeFormatter formatter) {
super(formatter);
}
}

Proper way to convert Spring HATEOAS Link to Object

I have a very simple controller the makes an HTTP request and receives some resources in HATEOAS format.
package com.provider.spring.controller;
import java.util.List;
import org.springframework.hateoas.Link;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.client.RestTemplate;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.provider.employee.EmployeeDTO;
import com.provider.http.HttpComponentsClientHttpRequestFactoryBasicAuth;
import com.provider.spring.rest.Resource;
#Controller
public class EmployeeController {
private static final String REL_SELF = "self";
private static final String REL_SEARCH = "search";
private static final String REL_EMPLOYEE = "employee";
private static final String RESOURCE_URI = "http://localhost:8080/employees";
private RestTemplate restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactoryBasicAuth("user", "password"));
private List<EmployeeDTO> employees;
#RequestMapping("/employees")
public String getAllEmployees() {
String result = null;
try {
String resultBody = restTemplate.getForObject(RESOURCE_URI, String.class);
ObjectMapper objectMapper = new ObjectMapper();
Resource<EmployeeDTO> resource = objectMapper.readValue(resultBody, Resource.class);
// Get objects with relation "employee"
for(Link l : resource.getLinks()) {
if(l.getRel().equals(REL_EMPLOYEE)) {
// TODO: Construct EmployeeDTO from Link.
// TODO: Add EmployeeDTO to list.
}
}
}
catch(Exception e) {
e.printStackTrace();
result = "error";
return result;
}
return result;
}
}
Is there an existing prefered or standard way of converting Links to Objects?
See here for details on the EmployeeDTO: https://gist.github.com/Xerosigma/64469a30355f5de0228a
A couple ways to do this with Jackson:
Without custom converter:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new Jackson2HalModule());
RestTemplate restTemplate= new RestTemplate();
String resultBody = restTemplate.getForObject(link.getHref(), String.class);
EmployeeDTO resource = objectMapper.readValue(resultBody, EmployeeDTO.class);
With custom converter:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new Jackson2HalModule());
RestTemplate restTemplate= new RestTemplate();
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(objectMapper);
restTemplate.setMessageConverters(Collections.<HttpMessageConverter<?>> singletonList(converter));
EmployeeDTO resource = restTemplate.getForObject(link.getHref(), EmployeeDTO.class);
You can register some MessageConverters in the constructor of the RestTemplate or use the setMessageConverters. Both take a List of HttpMessageConverters as a parameter.
If you have it configured like that, you can use the exchange or the getForObject-method with your expected resource-type. The RestTemplate will run through all the converters and check if it can convert the json/xml/... to your object.
Here is an example:
List<HttpMessageConverter<?>> converters = new LinkedList<>();
converters.add(new MappingJacksonHttpMessageConverter());
RestTemplate template = new RestTemplate(converters);
// or use the set method
template.setMessageConverters(converters);
// make url call
Resource<EmployeeDTO> resource = template.getForObject("url", Resource.class);

Resources