JAX-RS Jersey JSON ObjectMapper configuration ignored - json

I've the following code to change a property in Jackson. I'm annotating the classes with XMLRootElements and letting Jersey convert it to JSON, using jackson.
Classes are JAXB annotated.
#Provider
#Produces("application/json")
public class JacksonObjectMapper implements ContextResolver<ObjectMapper> {
private ObjectMapper objectMapper;
public JacksonObjectMapper() throws Exception {
objectMapper.configure( DeserializationConfig.Feature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
}
#Override
public ObjectMapper getContext(Class<?> type) {
return this.objectMapper;
}
}
The configuration above, works as expected if I use Jackson outside jersey (i.e: using his own function), but If I use it inside a Jersey app, the configuration options are ignored.
Is there a way to instruct Jersey to use my class to serialize / deserialize from XML to JSON?

Add this class to your javax.ws.rs.core.Application's classes list:
application.addClass(JacksonObjectMapper.class)

Related

Apache Camel Rest Custom Json Deserializer

I use Camel 2.16.0 for a Camel Rest project. I have introduced an abstract type that I need a custom deserializer to handle. This works as expected in my deserialization unit tests where I register my custom deserializer to the Objectmapper for the tests. To my understanding it is possible to register custom modules to the Jackson Objectmapper used by Camel as well (camel json).
My configuration:
...
<camelContext id="formsContext" xmlns="http://camel.apache.org/schema/spring">
...
<dataFormats>
<json id="json" library="Jackson" useList="true" unmarshalTypeName="myPackage.model.CustomDeserialized" moduleClassNames="myPackage.MyModule" />
</dataFormats>
</camelContext>
My module:
package myPackage;
import com.fasterxml.jackson.databind.module.SimpleModule;
public class MyModule extends SimpleModule {
public MyModule() {
super();
addDeserializer(CustomDeserialized.class, new MyDeserializer());
}
}
The Camel rest configuration:
restConfiguration()
.component("servlet")
.bindingMode(RestBindingMode.json)
.dataFormatProperty("prettyPrint", "true")
.contextPath("/")
.port(8080)
.jsonDataFormat("json");
When running the service and invoking a function that utilize the objectmapper I get the exception:
com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of myPackage.model.CustomDeserialized, problem: abstract types either need to be mapped to concrete types, have custom deserializer, or be instantiated with additional type information
Any suggestions on what is wrong with my setup?
I found this solution to the problem and used this implementation for my custom jackson dataformat:
public class JacksonDataFormatExtension extends JacksonDataFormat {
public JacksonDataFormatExtension() {
super(CustomDeserialized.class);
}
protected void doStart() throws Exception {
addModule(new MyModule());
super.doStart();
}
}

jackson-dataformat-xml turns #ResponseBody to XML

So I had a perfectly working Spring app. Most of my controller methods are for ajax calls that return JSON via #ResponseBody with the jackson api and returns my Java POJO to JSON.
I have a need to turn XML to JSON, so I find that Jackson has a tool for that, and I add this to my POM to use the library:
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.9.0</version>
</dependency>
So that I may use this:
XmlMapper xmlMapper = new XmlMapper();
JsonNode node = xmlMapper.readTree(sb.toString().getBytes());
But now the #ResponseBody is returning XML and not JSON. I Remove the dependency and the controllers return JSON again.
Any way to get both? I want the xmlMapper, and JSON from the response body.
jackson-dataformat-xml appears to be registering a MappingJackson2HttpMessageConverter with a XmlMapper, along with other HttpMessageConverters that work with XML. If you always intended to return JSON from your controllers, you can change what HttpMessageConverter your app uses by overriding configureMessageConverters
For Spring 5.0 and above,
#Configuration
public class HttpResponseConfig implements WebMvcConfigurer {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.removeIf(converter -> supportsXml(converter) || hasXmlMapper(converter));
}
private boolean supportsXml(HttpMessageConverter<?> converter) {
return converter.getSupportedMediaTypes().stream()
.map(MimeType::getSubtype)
.anyMatch(subType -> subType.equalsIgnoreCase("xml"));
}
private boolean hasXmlMapper(HttpMessageConverter<?> converter) {
return converter instanceof MappingJackson2HttpMessageConverter
&& ((MappingJackson2HttpMessageConverter)converter).getObjectMapper().getClass().equals(XmlMapper.class);
}
}
For older versions of Spring, replace implements WebMvcConfigurer with extends WebMvcConfigurerAdapter
Add Accept: application/json to HTTP request header.
Read this for an analysis of how Spring does content negotiation and allows producing either XML or JSON.
The simplest way is to add an extension at the URL: Instead of /path/resource use /path/resource.json
You may also add a format parameter e.g. /path/resource?format=json or pass an appropriate Accept header
In my case, the XmlMapper was actually inserted into the application context as an #Bean. The other solutions here did not work for me. It seems like one of those issues where context matters, so for people coming here from a different context than the other answerers, here's another angle: I had to insert my own ObjectMapper.
#Configuration
public class XmlMapperConfiguration {
#Bean // me, culprit
public XmlMapper xmlMapper() {
return new XmlMapper();
}
#Bean // to make sure the rest of the application still works with JSON
public ObjectMapper objectMapper() {
return new ObjectMapper();
}
}
I'm going to throw in an #Primary on the ObjectMapper one. It seems suspicious that Spring would even choose that one consistently. Since XmlMapper extends ObjectMapper, why would it not take that one, so #Primary won't hurt.

Use two differently configured ObjectMappers in one Spring Boot application

I am working on a middleware-app which deserializes values received via RestTemplate as json-String from a legacy-API (so, no influence on "their" data model and thus needing some custom config for my objectmapper consuming this api), and the app itself serves a restful API with (partially enriched and composited) data based on the legacydata as json, too.
Now, my legacy-Mapping-Classes' Constructors are all sharing a common structure like this at the moment:
...
private ObjectMapper mapper;
public MyMapper() {
this.mapper = new ObjectMapper();
this.mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
this.mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
...
because I use Jackson to de-serialize the json from the legacysystem. Basically I want to refactor this redundance using Springs DI Container.
So I tried to create my own Objectmapper #Component which just extends ObjectMapper like it is stated in some answers in this thread:
Configuring ObjectMapper in Spring - lets call it FromLegacyObjectMapper - instead of initializing my mapper in every class, so I created one and used
#Autowired
private FromLegacyObjectMapper
(or the constructorinjection-equivalent, but for simplicitys sake..).
But this had some serious sideeffects. Actually, I wasn't able to deserialize clientjson to viewmodels in my controllers anymore because of the rootvalue-wrapping, because the autowiring overwrites the spring boot standard objectmapper which I actually need when deserializing viewModels from my frontend.
I try to get it up and running like this:
frontend <---> My Api using Standard ObjectMapper <--> viewModel created by consuming legacy-Api-json using FromLegacyObjectMapper
So, what I surely could do is using a baseclass for my mappingclasses and just add the code above to the base constructor, and let every Mapperclass extend this base, but actually I hoped to find a way to use springs dependency injection container instead. I am out of ideas for now, so I hope anyone could help me out!
edit: To make it perhaps a bit clearer please see Moritz' answer below and our discussion in the comments. I am well aware I am able to use #Qualifier annotation, but this would just solve the problem if there is a way to add the #Qualifier to the standard objectmapper used in spring controllers. I'll do some research myself, but other answers are highly welcome.
I would try adding two different ObjectMappers to the Spring container. You could add something like this, for example to your Application class (assuming that is the one annotated with #SpringBootApplication):
#Bean
#Qualifier("fromLegacy")
public ObjectMapper fromLegacyObjectMapper() {
// create and customize your "from legacy" ObjectMapper here
return objectMapper;
}
#Bean
#Qualifier("default")
public ObjectMapper defaultObjectMapper() {
// create your default ObjectMapper here
return objectMapper;
}
Then you can inject the "from legacy" ObjectMapper in classes that use the legacy API like this:
public class SomeServiceUsingLegacyApi {
private final ObjectMapper objectMapper;
#Autowired
public SomeServiceUsingLegacyApi(#Qualifier("fromLegacy") ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
// [...]
}
And in other classes, using the other API accordingly:
public class SomeServiceUsingOtherApi {
private final ObjectMapper objectMapper;
#Autowired
public SomeServiceUsingOtherApi(#Qualifier("default") ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
// [...]
}

JAX-RS 2.0: How to register a Feature or a ContextResolver to have a custom JaxbAnnotationIntrospector introspection registered in the Mapper

I have correctly registered JacksonJaxbJsonProvider in my JAX-RS 2.0 application using:
public Set<Class<?>> getClasses() {
... classes.add(com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider.class);
}
Now I need to customise the Mapper it self used in the JAX-RS resources because I need to add SerializationFeature.FAIL_ON_EMPTY_BEANS.
I have seen examples of how to do this using a Feature or a custom Contextresolver, but I know how to add it to a Mapper I create in my source code but I cannot add it to the Mapper that the REST services use using:
classes.add(ObjectMapperContextResolver.class);
classes.add(MarshallingFeature.class);
Because the JAX-RS 2.0 app ignores those classes.
How can I add the Feature or ContextResolver in my JAX-RS application so they are recognised?
You could reuse the Jersey implementation custom application. More Info And using ContextResolver get the custom ObjectMapper to use.
Example:
public class MyApplication extends ResourceConfig {
public MyApplication() {
/* packages that contains JAX-RS components, registers them to use also */
packages("org.foo.rest;org.bar.rest");
/* here register the provider */
register(MapperProvider.class);
}
}
The MapperProvider Class.
#Provider
public class MapperProvider implements ContextResolver<ObjectMapper> {
#Override
public ObjectMapper getContext(Class<?> arg0) {
/* you can configure the mapper as you like */
return new ObjectMapper()
.registerModule(new JaxbAnnotationModule()
.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false));
}

Jackson: is it possible to replace the serializer set with #JsonSerialize annotation (e.g. with ObjectMapper)?

Quick question: is it possible to override #JsonSerialize annotation (using attribute) with ObjectMapper?
I'm have spring-security-oauth2 integrated and I want to customize the way OAuth2Exception is serialized to JSON format. The problem is that this class uses
#JsonSerialize(using = OAuth2ExceptionJackson2Serializer.class)
I tried registering custom serializer with:
SimpleModule module = new SimpleModule()
module.addSerializer(OAuth2Exception, new JsonSerializer<OAuth2Exception>() {
#Override
void serialize(OAuth2Exception value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
jgen.writeString('{"test":"test"}')
}
})
ObjectMapper objectMapper = new ObjectMapper()
objectMapper.registerModule(module)
but it didn't work - the serializer set with #JsonSerialize is used instead of the custom one.
Is there any other way to replace the serializer set with #JsonSerialize?
PS: the sample code is written in groovy
For such case Jackson has a mechanism called mix-in annotations.
You can create a class that overrides initial annotations.
#JsonSerialize(using=MySerializer.class)
public static abstract class OAuth2ExceptionMixIn {
}
Then register it in the object mapper:
objectMapper.addMixIn(OAuth2Exception.class, OAuth2ExceptionMixIn.class);
And that's it. Now Jackson should use your MySerializer instead of the initial OAuth2ExceptionJackson2Serializer
.