ObjectMapper configure GLOBAL LocalDateTime on Spring Java - spring

I'm trying to achieve something absurdly simple, that is set the global time format to all json serialized in the spring boot application... I've tried many suggestions from other questions but it looks like jackson chooses to ignore whatever configuration i set to object mapper, my code is
#Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("dd-MM-yyyy hh:mm")));
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("dd-MM-yyyy hh:mm")));
mapper.registerModule(javaTimeModule);
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
mapper.configure(SerializationFeature.WRITE_DATES_WITH_CONTEXT_TIME_ZONE, false);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return mapper;
}
if I autowire the ObjectMapper and use it manually like System.out.println(objectMapper.writeValueAsString(LocalDateTime.now())); I get the dates in the format I want.
But all my controllers keep generating json in this format 2022-06-30T22:44:11
All my entities and Dtos use LocalDateTime as time object...
What am I missing to make it work?
Please dont suggest annotate all my LocalDateTime to set the pattern, I want a configuration that I can set globally
thanks

Try annotating objectMapper configuration with #Primary.
Also article bellow suggests some solutions too https://www.baeldung.com/spring-boot-customize-jackson-objectmapper. If #Primary doesn't work, check that out

Related

Spring and serialization to Json - how to customize globally Jackson without Spring Boot

I'm using clean Spring MVC framework (v5.3.21) without Spring Boot.
I was working with Gson library, which was used by Spring to serialize view models, returned with request methods.
public class Coffee {
String name = "n";
String brand = "b";
}
#RequestMapping(value={"/coffe"}, method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public Coffee getCoffee() {
return new Coffee();
}
Recently I added Jackson (v 2.13.3) on the classpath and I've noticed serialization works much different. First of all - in Gson non-private field where serialized by default, now they are not visible at the client side.
I know I can add annotation
#JsonAutoDetect(fieldVisibility = Visibility.NON_PRIVATE)
to all the model classes, or change fields to public (Jackson default visibility for fields is PUBLIC, as far as I found out).
But I would like to change just once, globally, in configuration, without rewriting code of many
I tried many options, but none of them doesn't work without Spring Boot.
Do you know to change this default setting with clean Spring?
You can create ObjectMapper bean which can be global for application
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(mapper.getSerializationConfig().getDefaultVisibilityChecker()
.withFieldVisibility(JsonAutoDetect.Visibility.NON_PRIVATE));
return mapper;
}

RestTemplate getForObject to POJO

I'm using RestTemplate.getForObject() to retrieve json that includes a couple of objects and arrays, I only want to convert one of the objects inside this json to a POJO, I don't care about the other objects inside this json.
What is the proper way to approach this?
Edit:
Another approach from accepted answer, we can use jacksons ObjectMapper
#Autowired
private ObjectMapper jacksonObjectMapper;
then
LinkedHashMap obj1 = restTemplate.getForObject(uri, LinkedHashMap.class, params);
LinkedHashMap obj2 = (LinkedHashMap)test.get("flightStatuses");
Flight flight = jacksonObjectMapper.convertValue(obj2, Flight.class);
You get the idea, just get a generic datatype from your json structure then use ObjectMapper to convert it to the class you need.
One solution would be to create a wrapper class, which includes the POJO you want to deserialize and ignore all other properties using #JsonIgnoreProperties. You would then retrieve the wrapper object and get the POJO from it.
#JsonIgnoreProperties(ignoreUnknown=true)
public class Wrapper {
private MyPojo myPojo;
}
MyPojo myPojo = restTemplate.getForObject("url", Wrapper.class).getMyPojo();

How to register my custom MessageConverter to the SimpleMessageListenerContainerFactory?

I have customer converter that implements the MessageConverter interface. However, I dont see a way to register it with the SimpleMessageListenerContainerFactory. As a result I get a error when I try to read a message from SQS that is in the source format as it doesnt know how to convert it to the target object.
I looked through the SqsConfiguration class and I see that the simpleMessageListenerContainer bean being defined has a queueMessageHandler set on it. The QueueMessageHandler has resolvers on it, one of which is a CompositeMessageConverter which takes a Collection of MessageConverter types. I am guessing somehow I need to add my custom MessageConverter to this collection. I cant seem to get a handle to how I can do that.
Can someone help help me point to a wayI can register my customer MessageMapper?
From what I can tell, the only way to really do this is to create your own QueueMessageHandlerFactory with whatever resolvers/converters you need.
For example, add this to your #Configuration class:
#Bean
public QueueMessageHandlerFactory queueMessageHandlerFactory() {
List<MessageConverter> converters = ...
CompositeMessageConverter converter = new CompositeMessageConverter(converters);
QueueMessageHandlerFactory factory = new QueueMessageHandlerFactory();
factory.setArgumentResolvers(Arrays.asList(new PayloadArgumentResolver(converter));
return factory;
}
SqsConfiguration should pick up your QueueMessageHandlerFactory bean so it won't create one itself.
The JMS message is converted firstly. After that, MessageListenerContainer transmit the message to the registered listener. So you need register your converter before MessageListenerContainer get it. There is only in JmsTemplate or JmsMessagingTemplate call the method setMessageConverter or setJmsMessageConverter to configure your converter.

How do I add things to the /info endpoint in spring boot programmatically?

How do I add things to the /info endpoint in Spring Boot programmatically? The documentation states that this is possible for the /health endpoint through the use of HealthIndicator interface. Is there something for the /info endpoint as well?
I would like to add operating system name and version and other runtime info there.
In Spring Boot 1.4, you are able to declare InfoContributer beans to make this a whole lot easier:
#Component
public class ExampleInfoContributor implements InfoContributor {
#Override
public void contribute(Info.Builder builder) {
builder.withDetail("example",
Collections.singletonMap("key", "value"));
}
}
See http://docs.spring.io/spring-boot/docs/1.4.0.RELEASE/reference/htmlsingle/#production-ready-application-info-custom for more info.
The accepted answer actually clobbers the InfoEndpoint and does not add to it.
One way I found to add to the info is, in a #Configuration class, add an #Autowired method that adds extra properties following the info.* conventions to the environment. Then InfoEndpoint will pick them up when its invoked.
You can do something like the following:
#Autowired
public void setInfoProperties(ConfigurableEnvironment env) {
/* These properties will show up in the Spring Boot Actuator /info endpoint */
Properties props = new Properties();
props.put("info.timeZone", ZoneId.systemDefault().toString());
env.getPropertySources().addFirst(new PropertiesPropertySource("extra-info-props", props));
}
One way to do what you want (in the event that you have totally custom properties you need to display) is to declare a bean of type InfoEndpoint which will override the default.
#Bean
public InfoEndpoint infoEndpoint() {
final LinkedHashMap<String, Object> map = new LinkedHashMap<String, Object>();
map.put("test", "value"); //put whatever other values you need here
return new InfoEndpoint(map);
}
As you can see from the code above, the map can contain whatever info you need.
In the event that the data you want to show can be retrieved by the environment and is not custom, you do not need to override the InfoEndpoint bean, but you can simply add properties to the properties file with a prefix of info. One example where the OS name is evaluated is the following:
info.os = ${os.name}
In the code above, Spring Boot will evaluate the right-hand expression before returning the property in the /info endpoint.
A final note is that there is a ton of environment information that is already available in the /env endpoint
Update
As pointed out by #shabinjo, in newer Spring Boot versions there is no InfoEndpoint constructor that accepts a map.
You can however use the following snippet:
#Bean
public InfoEndpoint infoEndpoint() {
final Map<String, Object> map = new LinkedHashMap<String, Object>();
map.put("test", "value"); //put whatever other values you need here
return new InfoEndpoint(new MapInfoContributor(map));
}
The code above will completely override the default info that would end-up in /info.
To overcome this issue one could add the following bean
#Bean
public MapInfoContributor mapInfoContributor() {
return new MapInfoContributor(new HashMap<String, Object>() {{
put("test", "value");
}});
}
It should be possible to add a custom PropertySource inside an ApplicationListener to add custom info.* properties to the environment (see this answer for an example: How to Override Spring-boot application.properties programmatically)

Using jackson with spring mvc 2.5

sorry for general question, but I can't google anything on that, we use spring mvc 2.5 in our project, so there is no #ResponseBody annotation, how can I make something like this without it?
You could just return it as a string built with the Jackson object mapper:
public String getCustomDetails(#PathVariable String variable1) {
CustomDetails details = new CustomDetails(variable1);
ObjectMapper mapper = new ObjectMapper();
String result = null;
result = mapper.writeValueAsString(details);
return result;
}
That should work. Might have to surround the call to writeValueAsString in a try-catch.
Edit: I should clarify that "CustomDetails" and "variable1" are just example values... they could be anything.

Resources