This is about a REST PUT call with a JSON Object whith a root element, let's say, something like :
{"place":
{"name":"Here",
"address":
{"address":"somewhere under the rainbow",
"city":
{"name":"Oz",
"country":
{"name":"Oz",
"zone":"Far Far Away"}
}
}
}
}
defined with Jaxb Annotations
@XmlRootElement
public class PlaceOffline extends GlobalElement {
@XmlElement
public Address address;
}
and the superclass is
@XmlRootElement
public class GlobalElement implements Serializable {
@XmlElement
public Integer id;
@XmlElement
public String name;
@XmlElement
public String uniqueLabel;
}
I wrote a client and It miserabily failed with an
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "placeOffline"
I decide then to log the call and create a ContainerRequestFilter to do this
@Provider
public class RestLogFilter implements ContainerRequestFilter {
private Logger LOGGER_filter = Logger.getLogger(RestLogFilter.class.getName());
@Override
public void filter(ContainerRequestContext requestContext) throws IOException {
if (!MediaType.APPLICATION_JSON_TYPE.equals(requestContext.getMediaType())
|| requestContext.getEntityStream() == null) {
return;
}
InputStream inputStream = requestContext.getEntityStream();
byte[] bytes = IOUtils.readFully(inputStream , -1, true);
LOGGER_filter.info("Posted: " + new String(bytes, "UTF-8"));
requestContext.getEntityStream().mark(0);
}
}
And, guess what ?
It worked, unexpectedly ! No more Errors. The printing of the entity solves the problem. Awkward.
I see it like a workaround and it bothers me a little, has anyone an explanation ? A more elegant way to solve this issue ?
I am using WildFly 8.2
EDIT :
It worked with
public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {
private final ObjectMapper mapper;
public ObjectMapperContextResolver() {
mapper = new ObjectMapper();
mapper.enable(SerializationFeature.WRAP_ROOT_VALUE);
}
@Override
public ObjectMapper getContext(Class<?> type) {
return mapper;
}
}
You need to add Jackson to the application (just to compile), as you will need some of it's classes. Wildfly uses Jackson to deserialize JSON, and the default behavior is to fail when it sees properties on the JSON that are not modeled in the POJO. For example the following JSON
{"firstName": "stack", "lastName": "overflow"}
has a property "lastName"
which is not in the following POJO
public class Person {
private String firstName;
public void setFirstName(String firstName) { this.firstName = firstName; }
public String getFirstName() { return firstName; }
}
So it would fail with Jackson's default behavior.
For the dependency, you can add
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jackson2-provider</artifactId>
<version>3.0.9.Final</version>
<scope>provided</scope>
</dependency>
Notice the provided
scope, as Wildfly already has this dependency. If you are not using Maven, really all you need is jackson-databind
and jacskon-annotations
. Just grab the 2.4.x version. If you are using Jars, it iis important that these jars do not get packaged with your war, as the version may conflict with what Wildfly already has.
Now if you want to just ignore unknown properties for this one class, you can just us an annotation for the class
@JsonIgnoreProperties(ignoreUnknown = true)
public class PlaceOffline extends GlobalElement {
Or if you want to configure this behavior globally, you can see this answer. How to configure the ObjectMapper
is by the following
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
If the failure is due to the fact the the JSON is being wrapped, for example
{"person": {"firstName": "stack", "lastName": "overflow"}}
Then you need to set the property
mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
What this does is look for the value of the class name in either @XmlRootElement(name="person")
or @JsonRootName("person")
annotated on the class. For the JAXB annotation support, you will still need to configure the JAXB annotation module
mapper.registerModule(new JaxbAnnotationModule());
This module is contained in the jackson-module-jaxb-annotations
jar. It is already pulled in if you are using Maven (with the above dependency).