I'm trying to deserialise an a JSON input using a class that is annotated with #JsonComponent. However, even though I manually register this class, it is still not invoked when I call the endpoint with the input. I use openapi-generator-maven-plugin for generating controllers and aspectj for compile-time weaving. Can this interfere with the objects being recognised?
Note: The same setup works in a different poc application that I made but not this one. The main difference I see is the code generation. Hence I'm wondering if using the open-api-generation interferes with the Deserializer being recognised during the call to the Controller
#JsonComponent
public class UserDeserializer extends JsonDeserializer<User> {
#Override
public User deserialize (JsonParser jsonParser, DeserializationContext
deserializationContext) throws IOException {
log.info("***********INVOKED***************");
TreeNode treeNode = jsonParser.getCodec().readTree(jsonParser);
ObjectMapper objectMapper = new ObjectMapper();
//Some logic here
}
}
Config class has
#Bean
public Module userDeserializer() {
SimpleModule module = new SimpleModule();
module.addDeserializer(User.class, new UserDeserializer());
return module;
}
Related
I have a Spring Boot Library package common-library, within it contains a DTO class, say OrderDTO [package- com.example.common.dto].
I have two microservices - Core & Notification service. In Core service I have Order class in package com.example.core.domain.
In Notification service I added common-library external dependency and created a #FeignClient class
import com.example.common.dto.OrderDTO;
#FeignClient(name = "core")
public class CoreServiceClient {
#GetMapping("/api/v1/order/get/{id})
OrderDTO getOrderById(#PathVariable("id") String id);
}
Now when I call the getOrderById method from Notification service, I get the below error
InvalidTypeIdException: "Could not resolve type id 'com.example.core.domain.Order' as a subtype of `com.example.common.dto.OrderDTO`: no such class found"
Now I know one simple way to resolve this issue is by creating match class Order in package com.example.core.domain.
But I want to know if there are any workarounds without needing to create same class
I figured out the issue. I was adding MappingJackson2HttpMessageConverter with ObjectMapper bean as constructor parameter
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(0, new MappingJackson2HttpMessageConverter(objectMapper()));
}
Which was serializing the data type information in response. Removed the objectMapper()
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(0, new MappingJackson2HttpMessageConverter());
}
I am using Spring Boot framework and trying to create a structure where the developer can only return org.json.JSONObject instance. I have this endpoint declaration.
#RequestMapping(path = "/hello", method = RequestMethod.POST)
#ResponseBody
public org.json.JSONObject hello(HttpServletRequest request, HttpServletResponse response) throws IOException
This always returns {"empty":false} because Jackson used by the framework does not know how to serialize the org.json instance. I am trying to tell Jackson how to serialize the org.json instance by using the following dependency.
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-json-org</artifactId>
<version>2.13.0</version>
</dependency>
But I cannot get it work unless I change the return type to Map value which is not possible. Using
ObjectMapper mapper = JsonMapper.builder()
.addModule(new JsonOrgModule())
.build()
does not help. Is there a global ObjectMapper object that is used by Spring Boot where I can register the JsonOrgModule at the application startup? How can I use org.json.JSONObject return type using Spring Boot framework.
Thanks!
As per Spring Boot docs 4.3 Customize the Jackson ObjectMapper section:
Any beans of type com.fasterxml.jackson.databind.Module are
automatically registered with the auto-configured
Jackson2ObjectMapperBuilder and are applied to any ObjectMapper
instances that it creates. This provides a global mechanism for
contributing custom modules when you add new features to your
application.
Therefore, if you provide a #Bean of type JsonOrgModule it will be automatically applied to the default ObjectMapper created at startup.
For exmaple:
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public JsonOrgModule jsonOrgModule() {
return new JsonOrgModule();
}
}
#SpringBootTest
class ObjectMapperTests {
#Autowired
ObjectMapper defaultObjectMapper;
#Test
void defaultObjectMapperShouldWriteJsonObject() throws JSONException, JsonProcessingException {
// Given
var jsonObject = new JSONObject().put("username", "eHayik");
// When
var json = defaultObjectMapper.writeValueAsString(jsonObject);
// Then
assertThat(json).isEqualTo("{\"username\":\"eHayik\"}");
}
}
I am writing a spring boot command line tool that is supposed to interface with an API backend I already implemented. That API backend is built with spring data rest with the hateoas package, so it produces HAL message types.
In my CLI tool, I want to POST an entity that contains a list of other entities (one to many relation). For easier use, I wanted to use Resource types in the models to express relations and have a JSON serializer to transform the Resources into only their self hrefs.
My serializer works fine for one to one relations, but never gets calls to serialize arrays or any collection types.
This is what the API accepts when I POST an entity:
{
"property1": "value1",
"myrelation" : "http://localhohst:8080/relatedentities/1"
"mycollection": [
"http://localhost:8080/otherrelatedentities/2",
"http://localhost:8080/otherrelatedentities/3"
]
}
On the CLI side, I created a model entity in the CLI application like this:
#Getter #Setter
public class MyEntity {
private String property1;
#JsonSerialize(using = HateoasResourceIdSerializer.class)
private Resource<RelatedEnity> myrelation;
#JsonSerialize(using = HateoasResourceIdSerializer.class)
private List<Resource<OtherRelatedEntity>> mycollection;
}
I wrote this HateoasResourceIdSerializer to transform any Resource type into only its self href:
public class HateoasResourceIdSerializer extends StdSerializer<Resource<?>> {
private static final long serialVersionUID = 1L;
public HateoasResourceIdSerializer() {
this(null);
}
public HateoasResourceIdSerializer(Class<Resource<?>> t) {
super(t);
}
#Override
public void serialize(Resource<?> value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeString(value.getId().getHref());
}
}
Looking at the payload sent to the API backend, I can see that the "myrelation" property is set to the URL of the target entity while the "mycollection" property is always null.
I tried writing a 2nd Serializer that would accept Collection<Resource<?>> but that didnt get called either.
My expectation would be that the serializer above for Resource would be applied to arrays as well as any collection type.
EDIT:
I was asked to provide code to register serializers, so here it is. I added the two mixins as suggested in one of the answers below (hope I did it right) but did not see the expected behavior. I also assumed that due to the registration I could remove the #JsonSerialize(using = HateoasResource(s)IdSerializer.class) annotation from the properties. The current behavior is that those properties do not get rendered at all.
#SuppressWarnings("deprecation")
#SpringBootApplication
#EnableHypermediaSupport(type=EnableHypermediaSupport.HypermediaType.HAL)
public class Application extends WebMvcConfigurerAdapter implements ApplicationRunner {
public static void main(String[] args) {
SpringApplication.run(SwissArmyKnifeApplication.class, args);
}
#Override
public void run(ApplicationArguments args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
// ...
}
#Autowired
private HalHttpMessageConverter halHttpMessageConverter;
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(halHttpMessageConverter);
super.configureMessageConverters(converters);
}
}
#Configuration
public class HalHttpMessageConverter extends AbstractJackson2HttpMessageConverter {
public HalHttpMessageConverter() {
super(new ObjectMapper(), new MediaType("application", "hal+json", DEFAULT_CHARSET));
objectMapper.registerModule(new Jackson2HalModule());
objectMapper
.setHandlerInstantiator(new Jackson2HalModule.HalHandlerInstantiator(new DefaultRelProvider(), null, null));
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.addMixIn(Resource.class, ResourceMixIn.class);
objectMapper.addMixIn(Resources.class, ResourcesMixIn.class);
}
#Override
protected boolean supports(Class<?> clazz) {
return ResourceSupport.class.isAssignableFrom(clazz);
}
}
You need to properly register your custom serialiser using MixIn feature. Instead of annotating property, you need to annotate class which informs Jackson you want to use it in all scenarios, not only for MyEntity class.
Create MixIn interface:
#JsonSerialize(using = HateoasResourceIdSerializer.class)
interface ResourceMixIn {
}
And register it:
ObjectMapper mapper = JsonMapper.builder()
.addMixIn(Resource.class, ResourceMixIn.class).build();
See other questions how to configure Jackson mapper in Spring:
How do i use Jackson Json parsing in a spring project without any annotations?
Different JSON configuration in a Spring application for REST and Ajax serialization
Spring Boot custom serializer for Collection class
Spring Boot Jackson date and timestamp Format
You are not including code to indicate how you are registering serializer for your type so that could give the clue. But custom serializers definitely should be called for array, Collection and Map values as well as simple property values.
Registering separate serializer for Collection<Type> is not needed (and is actually bit trickier to do: possible, but more work due to nested type) and is not meant to be done just to support specific type in collection (but rather to support more special Collections, if any).
So please include code related to registration, as well as version of Jackson used (and obv. if not recent one, consider upgrading it first to see problem still persists).
This question already has answers here:
Can't set ProblemHandler to ObjectMapper in Spring Boot
(2 answers)
Closed 4 years ago.
As I understood, Spring is already providing a bean for Jackson ObjectMapper. Therefore, instead of creating a new bean, I'm trying to customize this bean.
From this blog post, and then this Github project I used Jackson2ObjectMapperBuilder bean to achieve this customization.
#Bean
public Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder(ApplicationContext context) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.findModulesViaServiceLoader(true);
return builder;
}
Then, I was trying to customize the deserializer in order to make it lenient: if an exception is raised when deserializing a property, I want the result object's property to be null and let the deserialization continue (default is to fail on first property that cannot be deserialized).
I've been able to achieve that with a class NullableFieldsDeserializationProblemHandler that extends DeserializationProblemHandler (I do not think the code is relevant but if needed, I can share it).
The simplest way to register this handler is to use the .addHandler() method of ObjectMapper. But of course, doing like this, I would need to set that every time I inject and use the ObjectMapper. I'd like to be able to configure handler so that every time the ObjectMapper is auto-wired, the handler is already present.
The best solution I came up with so far is to use a #PostConstruct annotation only to register the problem handler.
#Configuration
public class JacksonConfiguration implements InitializingBean {
#Autowired private ObjectMapper objectMapper;
#Bean
public Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder(ApplicationContext context) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.findModulesViaServiceLoader(true);
return builder;
}
#Override
public void afterPropertiesSet() {
objectMapper.addHandler(new NullableFieldsDeserializationProblemHandler());
}
}
But the problem of this solution is that it seems I can still access an autowired ObjectMapper that doesn't have yet registered the problem handler (I can see it happening after when I need it in debug mode).
Any idea how I should register this handler? I've noticed Jackson2ObjectMapperBuilder has a .handlerInstantiator() but I couldn't figure out how to use it.
Note I've also tried with Jackson2ObjectMapperBuilderCustomizer since I'm using Spring Boot but had no better results.
It's not possible to directly add a DeserializationProblemHandler to the ObjectMapper via a Jackson2ObjectMapperBuilder or Jackson2ObjectMapperBuilderCustomizer. The handlerInstanciator() method is for something else.
However, it's possible to do so by registering a Jackson module:
the builder has a modules() method
the module has access via setupModule() to a SetupContext instance, which has a addDeserializationProblemHandler() method
This works:
#Bean
public Jackson2ObjectMapperBuilderCustomizer customizer() {
return new Jackson2ObjectMapperBuilderCustomizer() {
#Override
public void customize(Jackson2ObjectMapperBuilder builder) {
builder.modules(new MyModule());
}
};
}
private static class MyModule extends SimpleModule {
#Override
public void setupModule(SetupContext context) {
// Required, as documented in the Javadoc of SimpleModule
super.setupModule(context);
context.addDeserializationProblemHandler(new NullableFieldsDeserializationProblemHandler());
}
}
What about writing a bean like this:
#Configuration
public class ObjectMapperConfiguration {
#Bean
ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
// jackson 1.9 and before
objectMapper.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// or jackson 2.0
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return objectMapper;
}
}
This is for global configuration. If, instead, what you want to do is to configure the feature for specific a class, use this annotation above the class definition:
#JsonIgnoreProperties(ignoreUnknown = true)
There is something that is not enough clear in this part of Spring Data Rest documentation:
The Spring Data REST exporter executes any discovered
ResourceProcessor`s before it creates the output representation.
For what I have noticed, it's true: ResourceProcessor are invoked during the handling of the request, after the completion of RepositoryEntityController respective method.
It does this by registering a Converter<Entity, Resource> instance
with an internal ConversionService.
I don't understand when it is used this Converter<Entity,Resource>.
This is the component responsible for creating the links to referenced
entities (e.g. those objects under the _links property in the object’s
JSON representation). It takes an #Entity and iterates over its
properties, creating links for those properties that are managed by a
Repository and copying across any embedded or simple properties.
Sure? I noticed that the _links to referenced entities are created in the RepositoryEntityController. I didn't see any other component that builds those links: no ConversionService or Converter are involved.
If your project needs to have output in a different format, however,
it’s possible to completely replace the default outgoing JSON
representation with your own. If you register your own
ConversionService in the ApplicationContext and register your own
Converter, then you can return a Resource
implementation of your choosing.
I don't undestand how is possible to do that.
I have tried to do exactly what is written in the documentation: I have registered my own ConversionService in the ApplicationContext and my own Converter.
I have registered the ConversionService in a custom class that extends RepositoryRestMvcConfiguration:
#Configuration
public class RepositoryConfiguration extends RepositoryRestMvcConfiguration {
#Autowired
AuthorConverter authorConverter;
#Bean(name="conversionService")
public ConversionService getConversionService() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
conversionService.addConverter(authorConverter);
return conversionService;
}
#Override
public DefaultFormattingConversionService defaultConversionService() {
return (DefaultFormattingConversionService) getConversionService();
}
}
This is the AuthorConverter:
#Component
public class AuthorConverter implements Converter<Author, Resource> {
#Override
public Resource convert(Author source) {
System.out.println("convert method of class AuthorConverter");
// still to be implemented
return null;
}
}
But the converter is never used: if I go the the /authors url, the JSON is solved as the standard representation, and the "convert" method of the converter is never invoked.
I want to understand (possibly with a working example) how have a custom converter that's being involved in the process of the output representation.
Thanks.
Does this article help?
Source: http://www.baeldung.com/spring-httpmessageconverter-rest
"We can customize the message converters by extending the WebMvcConfigurerAdapter class and overriding the configureMessageConverters method:
#EnableWebMvc
#Configuration
#ComponentScan({ "org.baeldung.web" })
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void configureMessageConverters(
List<HttpMessageConverter<?>> converters) {
messageConverters.add(createXmlHttpMessageConverter());
messageConverters.add(new MappingJackson2HttpMessageConverter());
super.configureMessageConverters(converters);
}
private HttpMessageConverter<Object> createXmlHttpMessageConverter() {
MarshallingHttpMessageConverter xmlConverter =
new MarshallingHttpMessageConverter();
XStreamMarshaller xstreamMarshaller = new XStreamMarshaller();
xmlConverter.setMarshaller(xstreamMarshaller);
xmlConverter.setUnmarshaller(xstreamMarshaller);
return xmlConverter;
}
}