I'm using Jackson 2.7.5 with Jersey 2.25.1. I'm trying to fix existing production code that is now failing with "UnrecognizedPropertyException: Unrecognized field" when it gets an unexpected field in the JSON input.
In researching this, I found several old posts (5+ years) suggesting various fixes that were very different from my current code. I didn't pay much attention to these, because they were for old versions of Jackson/Jersey. And more recent suggestions, including Jersey's own documentation (https://jersey.github.io/documentation/latest/media.html#json.jackson), look very similar to what I already have in place. In fact, to my eyes, it looks like my existing code is already following the current practice. However, Jersey seems to be ignoring my custom ObjectMapper setting of...
DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false
I'm pretty sure that setting should fix the error, but Jersey seems to be using a default ObjectMapper instead of my custom settings.
First, here is the dependency information, which I believe matches what is shown in the Jersey documentation (https://jersey.github.io/documentation/latest/media.html#json.jackson).
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>${jersey.version}</version>
</dependency>
Here is the call that is returning the error:
// this will throw an exception if it can't convert the string to the class
PropSearchResponse propResponse = null;
try {
propResponse = getResponse.readEntity(PropSearchResponse.class);
} catch(final ProcessingException e) {
throw new ProcessResultException(Code.FAILED, "failed to map from prop response", e);
}
Here is the original code for my custom ObjectMapper:
#Provider()
#Produces(value = MediaType.APPLICATION_JSON)
public class OutMapperProvider implements ContextResolver<ObjectMapper> {
private final ObjectMapper mapper;
public OutMapperProvider() {
mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.configure(JsonGenerator.Feature.ESCAPE_NON_ASCII, true);
mapper.setSerializationInclusion(Include.NON_NULL);
mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"));
}
#Override()
public ObjectMapper getContext(final Class<?> type) {
return mapper;
}
}
Here is the example from the Jersey documentation:
#Provider
public class MyObjectMapperProvider implements ContextResolver<ObjectMapper> {
final ObjectMapper defaultObjectMapper;
public MyObjectMapperProvider() {
defaultObjectMapper = createDefaultMapper();
}
#Override
public ObjectMapper getContext(Class<?> type) {
return defaultObjectMapper;
}
private static ObjectMapper createDefaultMapper() {
final ObjectMapper result = new ObjectMapper();
result.configure(Feature.INDENT_OUTPUT, true);
return result;
}
}
I have tried the Jersey example (changing the names to match mine, of course) as well as several other examples I found online. The Jersey example does the customization after the #Override. Most other examples do it before the #Override, but they all seem substantially similar to each other and to my existing code. But it doesn't seem to make any difference. No matter what I have tried, the custom configuration is ignored and Jersey calls a default ObjectMapper, which fails on unexpected JSON fields.
Disclaimer: This is my first experience with both Jersey and Jackson. I don't have a good understanding of the underlying mechanism yet. I'm just trying to follow the patterns of the examples.
Update: I believe the code above is basically correct. But Paul's comment below says that I need to register the custom ObjectMapper. I have tried reproducing several examples I have found on the web (Example 4.2 at https://docs.huihoo.com/jersey/2.13/deployment.html#environmenmt.appmodel, for example), but without success. For my current attempt, I have tried adding a new MyApplication class to an existing config package (com.dmx.repl.config) using Jersey's ResourceConfig. The code is below. But still, it is not working.
Edit: Ignore this code, it didn't work. See solution below.
package com.dmx.repl.config;
import org.glassfish.jersey.server.ResourceConfig;
import com.dmx.repl.commons.OutMapperProvider;
/**
*
* #author Greg
* #version 1.0
*/
// Attempt to register custom ObjectMapper
public class MyApplication extends ResourceConfig {
public MyApplication() {
// I've tried both of these.
//register(OutMapperProvider.class);
packages("com.dmx.repl.commons");
}
}
It's working now. Jersey is now recognizing the custom ObjectMapper, which is configured to ignore unknown JSON fields with "FAIL_ON_UNKNOWN_PROPERTIES, false".
The ObjectMapper code above is correct. The problem (as suggested by Paul in the comments) was that the client had not registered the custom ObjectMapper. This was fixed very simply, by adding the following line to the client setup method, following client setup with ClientBuilder.
this.client.register(OutMapperProvider.class);
Related
I tried different approaches that I found here on Stackoverflow. This is the way I know how to use a mapper with MapStruct.
I have a Mapper class like this:
#Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface DummyMapper {
DummyMapper INSTANCE = Mappers.getMapper(DummyMapper.class);
DummyResponseApi modelToApi(DummyResponse DummyResponseModel);
}
And my Unit test:
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {DummyMapper.class})
class ServiceClassTest {
private DummyService service;
}
My Service method that I want to test:
public DummyModelApi getSomething() {
DummyModel mapMe = new DummyModel();
return DummyMapper.INSTANCE.modelToApi(mapMe);
}
In older projects I did it the same way like this and had no problems. Now I'm using it in a new project with Spring Boot 2.5.6 and MapStruct 1.5.0.Beta1.
With using #SpringBootTest, as far as I know, Spring is actually starting the application and should create the Mapper class, so I don't understand, why the Mapper is always null?!
When I remove the DummyMapper.class in #SpringBootTest, an error appears with "Failed to load application context". That shows me, that the mapper is recognized.
Another thing that I find strange is, that I must use "unmappedTargetPolicy = ReportingPolicy.IGNORE" in my mapper, otherwise I get the error message "Unmapped properties could not found" or something, even though there is definetly the property with the same name in both models. This was always no problem in older projects, don't know why MapStruct is doing weird things now.
Omg, I found the solution. Now I know why I had to add "#Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE)" in my mapper.
Found the solution here: https://stackoverflow.com/a/69649688/8743351
Lombok needs to be before mapstruct in pom.xml/gradle.build.
I'm having a simple Spring Boot application with one REST endpoint to return a "Job" object, which contains a list of polymorphics, next to other stuff.
We go Code First approach and try to create the API models to fit our needs. But the generated Api Doc does not represent our model the in it's full complexity, as it does not resolve the list of polymorphics.
The Job object looks like
#Data // Lombok Getters and Setters
public final class Job {
private String foo;
private String bar;
private List<Condition> conditionList;
}
Condition is a parent object for a set of different conditions
public abstract class Condition {
}
Two example implementations of a Condition would be
#Data
public final class Internal extends Condition {
private String nodeId;
}
and
#Data
public final class Timed extends Condition {
private ZonedDateTime timestamp;
}
The REST controller is stupidly simple:
#RestController
#RequestMapping("/hello")
public class MyController {
#GetMapping
public ResponseEntity<Job> getJob() {
return new ResponseEntity<>(new Job(), HttpStatus.OK);
}
}
Now, when I open the Swagger UI and look at the generated definition, the element conditionList is an empty object {}
I tried to use the #JsonSubTypes and #ApiModel on the classed, but there was no difference in the output. I might not have used them correctly, or maybe Swagger is just not able to fulfill the job, or maybe I'm just blind or stupid.
How can I get Swagger to include the Subtypes into the generated api doc?
We "fixed" the problem by changing the structure. So it's more of a workaround.
Instead of using a List of polymorphics, we now use a "container" class, which contains each type as it's own type.
The Condition object became a "container" or "manager" class, instead of a List.
In the Job class, the field is now defined as:
private Condition condition;
The Condition class itself is now
public final class Condition{
private List<Internal> internalConditions;
// etc...
}
And, as example, the Internal lost it's parent type and is now just
public final class Internal{
// Logic...
}
The Swagger generated JSON now looks like this (excerpt):
"Job": {
"Condition": {
"Internal": {
}
"External": {
}
//etc...
}
}
Useful display of polymorphic responses in Swagger UI with Springfox 2.9.2 seems hard (impossible?). Workaround feels reasonable.
OpenAPI 3.0 appears to improve support for polymorphism. To achieve your original goal, I would either
Wait for Springfox to get Open API 3.0 support (issue 2022 in Springfox Github). Unfortunately, the issue has been open since Sept 2017 and there is no indication of Open API 3.0 support being added soon (in Aug 2019).
Change to Spring REST Docs, perhaps adding the restdocs-api-spec extension to generate Open API 3.0.
We have run into similar problems with polymorphism but have not yet attempted to implement a solution based on Spring REST Docs + restdocs-api-spec.
I am evaluating using Apache AVRO for my Jersey REST services. I am using Springboot with Jersey REST.
Currently I am accepting JSON as input which are converted to Java Pojos using the Jackson object mapper.
I have looked in different places but I cannot find any example that is using Apache AVRO with a Jersey end point.
I have found this Github repository (https://github.com/FasterXML/jackson-dataformats-binary/) which has Apache AVRO plugin.
I still cannot find any good example as how to integrate this. Has anyone used Apache AVRO with Jersey? If yes, is there any example I can use?
To start , two things need to happen:
You need to develop a custom ObjectMapper after the fashion of the Avro schema format
You need to supply that custom ObjectMapper to Jersey.
That should look something like this:
#Provider
public class AvroMapperProvider implements ContextResolver<ObjectMapper> {
final AvroMapper avroMapper = new AvroMapper();
#Override
public ObjectMapper getContext(Class<?> type) {
return avroMapper;
}
}
Configure your application to use Jackson as the message handler:
public class MyApplication extends ResourceConfig {
public MyApplication() {
super(JacksonFeature.class,AvroMapperProvider.class);
}
}
Alternatively, you can implement a custom MessageBodyReader and MessageBodyWriter that allows you to directly process the payloads on the way in and out:
public class AvroMessageReader implements MessageBodyReader<Person> {
AvroSchema schema;
final AvroMapper avroMapper = new AvroMapper();
public AvroMessageReader(){
schema = avroMapper.schemaFor(Person.class); //generates an Avro schema from the POJO class.
}
#Override
public boolean isReadable(Class<?> type, Type type1, Annotation[] antns, MediaType mt) {
return type == Person.class; //determines that this reader can handle the Person class.
}
#Override
public Person readFrom(Class<Person> type, Type type1, Annotation[] antns, MediaType mt, MultivaluedMap<String, String> mm, InputStream in) throws IOException, WebApplicationException {
return avroMapper.reader(schema).readValue(in);
}
}
Here, we generate an avro schema from a hypothetical Person class. The JAX-RS runtime will select this reader based on the response from isReadable.
You can then inject the MessageBodyWorkers component into your service implementation class:
#Path("app")
public static class BodyReaderTest{
#Context
private MessageBodyWorkers workers;
#POST
#Produces("avro/binary")
#Consumes("avro/binary")
public String processMessage() {
workers.getMessageBodyReader(Person.class, Person.class, new Annotation[]{}, MediaType.APPLICATION_JSON_TYPE);
}
}
To answer your last comment: Setting the mime type on your handler to the recommended avro/binary ought to do it.
There is a comprehensive demo (which I wrote) on how to use avro in a JAX-RS REST service at. The JAX-RS message body readers and writers for avro are implemented at and they do support avro binary, json, idiomatic json, csv where applicable. They do provide full support for schema evolution and projections (via the http accept header). There is a list of articles that explain in more detail the demonstrated concepts at. Also this demo project runs live on GKE at, you can browse at the openapi at. Avro is being used in the project for everything, for logs, for metrics, for profiling.
I need to serialize my POJO object that relies on JAXB annotations. I can easily do this with Jackson (adding JaxbAnnotationIntrospector). Is possible without any explicit coding?
It would be really nice to write it fluently akin to this:
port(Integer.valueOf(port)).
log().all().
contentType(ContentType.JSON).
body(criteria, ObjectMapperType.JACKSON_2)
Yet this one ignores JAXB. I want to find some neat and clean solution. Jackson + JAXB is very common practice, it will be such a shame if RestAssured is not equipped with it under the hood. I found something like this on the forum:
RestAssured.config = RestAssuredConfig.config().objectMapperConfig(new ObjectMapperConfig().jackson2ObjectMapperFactory(
new Jackson2ObjectMapperFactory() {
#Override
public ObjectMapper create(Class aClass, String s) {
FilterProvider filter = new SimpleFilterProvider().addFilter(...);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setFilters(filter);
return objectMapper;
}
}
));
but this will be my last resort.
If this is a common use case please add it as an issue and I'll try to integrate it as default.
How can I make my object mapper work in situation when there is another object mapper defined in jar from dependencies ?
I'm trying to use Swagger with Jersey 2 which is being run under Jetty. The problem is that as soon as I add Swagger JAX-RX jar into classpath my object mapper is not discovered therefore I lose custom serialization of my objects.
Here is how my object mapper defined
#Provider
public class ObjectMapperProvider implements ContextResolver<ObjectMapper> {
}
I've posted issue to Swagger's maintainers where you could read details.
After hours of debugging in internals of Jersey I found that Swagger's own object mapper com.wordnik.swagger.jaxrs.json.JacksonJsonProvider calls super.setMapper(commonMapper) that sets non-null value to ProviderBase._mapperConfig._mapper. Later when http request handler attempts to serialize instance of my class call ends up in ProviderBase.locateMapper which has following body
public MAPPER locateMapper(Class<?> type, MediaType mediaType)
{
// First: were we configured with a specific instance?
MAPPER m = _mapperConfig.getConfiguredMapper();
if (m == null) {
// If not, maybe we can get one configured via context?
m = _locateMapperViaProvider(type, mediaType);
if (m == null) {
// If not, let's get the fallback default instance
m = _mapperConfig.getDefaultMapper();
}
}
return m;
}
in correct code-flow _mapperConfig.getConfiguredMapper() returns null which subsequently causes invocation of _locateMapperViaProvider which finds my custom mapper. With Swagger it defaults to com.fasterxml.jackson.jaxrs.json.JsonMapperConfigurator and my custom json serializers never get invoked.
I created small project which reproduces this problem here.
How would you guys suggest to fix this ? I could probably specify deserializer on each property of type TTLocalDate but it'll pollute the code :(
As noted by fehguy in the issue report, using the latest Swagger release and using the SwaggerSerializers should fix this issue. Where previously the Swagger JacksonJsonProvider would be used for all serializions, the SwaggerSerializers is only used for the Swagger model object
public class SwaggerSerializers implements MessageBodyWriter<Swagger> {
#Override
public boolean isWriteable(Class type, Type genericType, Annotation[] annotations,
MediaType mediaType) {
return Swagger.class.isAssignableFrom(type);
}